/* Minification failed. Returning unminified contents.
(102706,35-36): run-time error JS1003: Expected ':': }
(102723,47-48): run-time error JS1195: Expected expression: >
(102723,62-63): run-time error JS1004: Expected ';': )
(102733,10-11): run-time error JS1004: Expected ';': )
(102736,5-6): run-time error JS1002: Syntax error: }
(102756,1-2): run-time error JS1002: Syntax error: }
(102757,35-36): run-time error JS1195: Expected expression: )
(102757,37-38): run-time error JS1004: Expected ';': {
(102845,2-3): run-time error JS1195: Expected expression: )
(102847,42-43): run-time error JS1195: Expected expression: )
(102847,44-45): run-time error JS1004: Expected ';': {
(102924,2-3): run-time error JS1195: Expected expression: )
(102926,40-41): run-time error JS1195: Expected expression: )
(102926,42-43): run-time error JS1004: Expected ';': {
(102985,2-3): run-time error JS1195: Expected expression: )
(102987,28-29): run-time error JS1197: Too many errors. The file might not be a JavaScript file: (
(102982,3,102984,4): run-time error JS1018: 'return' statement outside of function: return {
      initialize: initialize,
  }
(102921,5,102923,6): run-time error JS1018: 'return' statement outside of function: return {
        initialize: initialize,
    }
(102840,5,102844,6): run-time error JS1018: 'return' statement outside of function: return {
        initialize: initialize,
        getAvatar: getAvatar,
        getHeaderNoticeCount: getHeaderNoticeCount,
    }
(102752,5,102755,6): run-time error JS1018: 'return' statement outside of function: return {
        initializeActiveEventsDialog: initializeActiveEventsDialog,
        enableEventAdd: enableEventAdd
    }
 */
/*
 * jQuery UI Touch Punch 0.2.2
 *
 * Copyright 2011, Dave Furfero
 * Dual licensed under the MIT or GPL Version 2 licenses.
 *
 * Depends:
 *  jquery.ui.widget.js
 *  jquery.ui.mouse.js
 */
(function(b){b.support.touch="ontouchend" in document;if(!b.support.touch){return;}var c=b.ui.mouse.prototype,e=c._mouseInit,a;function d(g,h){if(g.originalEvent.touches.length>1){return;}g.preventDefault();var i=g.originalEvent.changedTouches[0],f=document.createEvent("MouseEvents");f.initMouseEvent(h,true,true,window,1,i.screenX,i.screenY,i.clientX,i.clientY,false,false,false,false,0,null);g.target.dispatchEvent(f);}c._touchStart=function(g){var f=this;if(a||!f._mouseCapture(g.originalEvent.changedTouches[0])){return;}a=true;f._touchMoved=false;d(g,"mouseover");d(g,"mousemove");d(g,"mousedown");};c._touchMove=function(f){if(!a){return;}this._touchMoved=true;d(f,"mousemove");};c._touchEnd=function(f){if(!a){return;}d(f,"mouseup");d(f,"mouseout");if(!this._touchMoved){d(f,"click");}a=false;};c._mouseInit=function(){var f=this;f.element.bind("touchstart",b.proxy(f,"_touchStart")).bind("touchmove",b.proxy(f,"_touchMove")).bind("touchend",b.proxy(f,"_touchEnd"));e.call(f);};})(jQuery);;
/*!
 * sly 1.6.0 - 17th Jul 2015
 * https://github.com/darsain/sly
 *
 * Licensed under the MIT license.
 * http://opensource.org/licenses/MIT
 */
;(function ($, w, undefined) {
	'use strict';

	var pluginName = 'sly';
	var className  = 'Sly';
	var namespace  = pluginName;

	// Local WindowAnimationTiming interface
	var cAF = w.cancelAnimationFrame || w.cancelRequestAnimationFrame;
	var rAF = w.requestAnimationFrame;

	// Support indicators
	var transform, gpuAcceleration;

	// Other global values
	var $doc = $(document);
	var dragInitEvents = 'touchstart.' + namespace + ' mousedown.' + namespace;
	var dragMouseEvents = 'mousemove.' + namespace + ' mouseup.' + namespace;
	var dragTouchEvents = 'touchmove.' + namespace + ' touchend.' + namespace;
	var wheelEvent = (document.implementation.hasFeature('Event.wheel', '3.0') ? 'wheel.' : 'mousewheel.') + namespace;
	var clickEvent = 'click.' + namespace;
	var mouseDownEvent = 'mousedown.' + namespace;
	var interactiveElements = ['INPUT', 'SELECT', 'BUTTON', 'TEXTAREA'];
	var tmpArray = [];
	var time;

	// Math shorthands
	var abs = Math.abs;
	var sqrt = Math.sqrt;
	var pow = Math.pow;
	var round = Math.round;
	var max = Math.max;
	var min = Math.min;

	// Keep track of last fired global wheel event
	var lastGlobalWheel = 0;
	$doc.on(wheelEvent, function (event) {
		var sly = event.originalEvent[namespace];
		var time = +new Date();
		// Update last global wheel time, but only when event didn't originate
		// in Sly frame, or the origin was less than scrollHijack time ago
		if (!sly || sly.options.scrollHijack < time - lastGlobalWheel) lastGlobalWheel = time;
	});

	/**
	 * Sly.
	 *
	 * @class
	 *
	 * @param {Element} frame       DOM element of sly container.
	 * @param {Object}  options     Object with options.
	 * @param {Object}  callbackMap Callbacks map.
	 */
	function Sly(frame, options, callbackMap) {
		// Extend options
		var o = $.extend({}, Sly.defaults, options);

		// Private variables
		var self = this;
		var parallax = isNumber(frame);

		// Frame
		var $frame = $(frame);
		var $slidee = o.slidee ? $(o.slidee).eq(0) : $frame.children().eq(0);
		var frameSize = 0;
		var slideeSize = 0;
		var pos = {
			start: 0,
			center: 0,
			end: 0,
			cur: 0,
			dest: 0
		};

		// Scrollbar
		var $sb = $(o.scrollBar).eq(0);
		var $handle = $sb.children().eq(0);
		var sbSize = 0;
		var handleSize = 0;
		var hPos = {
			start: 0,
			end: 0,
			cur: 0
		};

		// Pagesbar
		var $pb = $(o.pagesBar);
		var $pages = 0;
		var pages = [];

		// Items
		var $items = 0;
		var items = [];
		var rel = {
			firstItem: 0,
			lastItem: 0,
			centerItem: 0,
			activeItem: null,
			activePage: 0
		};

		// Styles
		var frameStyles = new StyleRestorer($frame[0]);
		var slideeStyles = new StyleRestorer($slidee[0]);
		var sbStyles = new StyleRestorer($sb[0]);
		var handleStyles = new StyleRestorer($handle[0]);

		// Navigation type booleans
		var basicNav = o.itemNav === 'basic';
		var forceCenteredNav = o.itemNav === 'forceCentered';
		var centeredNav = o.itemNav === 'centered' || forceCenteredNav;
		var itemNav = !parallax && (basicNav || centeredNav || forceCenteredNav);

		// Miscellaneous
		var $scrollSource = o.scrollSource ? $(o.scrollSource) : $frame;
		var $dragSource = o.dragSource ? $(o.dragSource) : $frame;
		var $forwardButton = $(o.forward);
		var $backwardButton = $(o.backward);
		var $prevButton = $(o.prev);
		var $nextButton = $(o.next);
		var $prevPageButton = $(o.prevPage);
		var $nextPageButton = $(o.nextPage);
		var callbacks = {};
		var last = {};
		var animation = {};
		var move = {};
		var dragging = {
			released: 1
		};
		var scrolling = {
			last: 0,
			delta: 0,
			resetTime: 200
		};
		var renderID = 0;
		var historyID = 0;
		var cycleID = 0;
		var continuousID = 0;
		var i, l;

		// Normalizing frame
		if (!parallax) {
			frame = $frame[0];
		}

		// Expose properties
		self.initialized = 0;
		self.frame = frame;
		self.slidee = $slidee[0];
		self.pos = pos;
		self.rel = rel;
		self.items = items;
		self.pages = pages;
		self.isPaused = 0;
		self.options = o;
		self.dragging = dragging;

		/**
		 * Loading function.
		 *
		 * Populate arrays, set sizes, bind events, ...
		 *
		 * @param {Boolean} [isInit] Whether load is called from within self.init().
		 * @return {Void}
		 */
		function load(isInit) {
			// Local variables
			var lastItemsCount = 0;
			var lastPagesCount = pages.length;

			// Save old position
			pos.old = $.extend({}, pos);

			// Reset global variables
			frameSize = parallax ? 0 : $frame[o.horizontal ? 'width' : 'height']();
			sbSize = $sb[o.horizontal ? 'width' : 'height']();
			slideeSize = parallax ? frame : $slidee[o.horizontal ? 'outerWidth' : 'outerHeight']();
			pages.length = 0;

			// Set position limits & relatives
			pos.start = 0;
			pos.end = max(slideeSize - frameSize, 0);

			// Sizes & offsets for item based navigations
			if (itemNav) {
				// Save the number of current items
				lastItemsCount = items.length;

				// Reset itemNav related variables
				$items = $slidee.children(o.itemSelector);
				items.length = 0;

				// Needed variables
				var paddingStart = getPx($slidee, o.horizontal ? 'paddingLeft' : 'paddingTop');
				var paddingEnd = getPx($slidee, o.horizontal ? 'paddingRight' : 'paddingBottom');
				var borderBox = $($items).css('boxSizing') === 'border-box';
				var areFloated = $items.css('float') !== 'none';
				var ignoredMargin = 0;
				var lastItemIndex = $items.length - 1;
				var lastItem;

				// Reset slideeSize
				slideeSize = 0;

				// Iterate through items
				$items.each(function (i, element) {
					// Item
					var $item = $(element);
					var rect = element.getBoundingClientRect();
					var itemSize = round(o.horizontal ? rect.width || rect.right - rect.left : rect.height || rect.bottom - rect.top);
					var itemMarginStart = getPx($item, o.horizontal ? 'marginLeft' : 'marginTop');
					var itemMarginEnd = getPx($item, o.horizontal ? 'marginRight' : 'marginBottom');
					var itemSizeFull = itemSize + itemMarginStart + itemMarginEnd;
					var singleSpaced = !itemMarginStart || !itemMarginEnd;
					var item = {};
					item.el = element;
					item.size = singleSpaced ? itemSize : itemSizeFull;
					item.half = item.size / 2;
					item.start = slideeSize + (singleSpaced ? itemMarginStart : 0);
					item.center = item.start - round(frameSize / 2 - item.size / 2);
					item.end = item.start - frameSize + item.size;

					// Account for slidee padding
					if (!i) {
						slideeSize += paddingStart;
					}

					// Increment slidee size for size of the active element
					slideeSize += itemSizeFull;

					// Try to account for vertical margin collapsing in vertical mode
					// It's not bulletproof, but should work in 99% of cases
					if (!o.horizontal && !areFloated) {
						// Subtract smaller margin, but only when top margin is not 0, and this is not the first element
						if (itemMarginEnd && itemMarginStart && i > 0) {
							slideeSize -= min(itemMarginStart, itemMarginEnd);
						}
					}

					// Things to be done on last item
					if (i === lastItemIndex) {
						item.end += paddingEnd;
						slideeSize += paddingEnd;
						ignoredMargin = singleSpaced ? itemMarginEnd : 0;
					}

					// Add item object to items array
					items.push(item);
					lastItem = item;
				});

				// Resize SLIDEE to fit all items
				$slidee[0].style[o.horizontal ? 'width' : 'height'] = (borderBox ? slideeSize: slideeSize - paddingStart - paddingEnd) + 'px';

				// Adjust internal SLIDEE size for last margin
				slideeSize -= ignoredMargin;

				// Set limits
				if (items.length) {
					pos.start =  items[0][forceCenteredNav ? 'center' : 'start'];
					pos.end = forceCenteredNav ? lastItem.center : frameSize < slideeSize ? lastItem.end : pos.start;
				} else {
					pos.start = pos.end = 0;
				}
			}

			// Calculate SLIDEE center position
			pos.center = round(pos.end / 2 + pos.start / 2);

			// Update relative positions
			updateRelatives();

			// Scrollbar
			if ($handle.length && sbSize > 0) {
				// Stretch scrollbar handle to represent the visible area
				if (o.dynamicHandle) {
					handleSize = pos.start === pos.end ? sbSize : round(sbSize * frameSize / slideeSize);
					handleSize = within(handleSize, o.minHandleSize, sbSize);
					$handle[0].style[o.horizontal ? 'width' : 'height'] = handleSize + 'px';
				} else {
					handleSize = $handle[o.horizontal ? 'outerWidth' : 'outerHeight']();
				}

				hPos.end = sbSize - handleSize;

				if (!renderID) {
					syncScrollbar();
				}
			}

			// Pages
			if (!parallax && frameSize > 0) {
				var tempPagePos = pos.start;
				var pagesHtml = '';

				// Populate pages array
				if (itemNav) {
					$.each(items, function (i, item) {
						if (forceCenteredNav) {
							pages.push(item.center);
						} else if (item.start + item.size > tempPagePos && tempPagePos <= pos.end) {
							tempPagePos = item.start;
							pages.push(tempPagePos);
							tempPagePos += frameSize;
							if (tempPagePos > pos.end && tempPagePos < pos.end + frameSize) {
								pages.push(pos.end);
							}
						}
					});
				} else {
					while (tempPagePos - frameSize < pos.end) {
						pages.push(tempPagePos);
						tempPagePos += frameSize;
					}
				}

				// Pages bar
				if ($pb[0] && lastPagesCount !== pages.length) {
					for (var i = 0; i < pages.length; i++) {
						pagesHtml += o.pageBuilder.call(self, i);
					}
					$pages = $pb.html(pagesHtml).children();
					$pages.eq(rel.activePage).addClass(o.activeClass);
				}
			}

			// Extend relative variables object with some useful info
			rel.slideeSize = slideeSize;
			rel.frameSize = frameSize;
			rel.sbSize = sbSize;
			rel.handleSize = handleSize;

			// Activate requested position
			if (itemNav) {
				if (isInit && o.startAt != null) {
					activate(o.startAt);
					self[centeredNav ? 'toCenter' : 'toStart'](o.startAt);
				}
				// Fix possible overflowing
				var activeItem = items[rel.activeItem];
				slideTo(centeredNav && activeItem ? activeItem.center : within(pos.dest, pos.start, pos.end));
			} else {
				if (isInit) {
					if (o.startAt != null) slideTo(o.startAt, 1);
				} else {
					// Fix possible overflowing
					slideTo(within(pos.dest, pos.start, pos.end));
				}
			}

			// Trigger load event
			trigger('load');
		}
		self.reload = function () { load(); };

		/**
		 * Animate to a position.
		 *
		 * @param {Int}  newPos    New position.
		 * @param {Bool} immediate Reposition immediately without an animation.
		 * @param {Bool} dontAlign Do not align items, use the raw position passed in first argument.
		 *
		 * @return {Void}
		 */
		function slideTo(newPos, immediate, dontAlign) {
			// Align items
			if (itemNav && dragging.released && !dontAlign) {
				var tempRel = getRelatives(newPos);
				var isNotBordering = newPos > pos.start && newPos < pos.end;

				if (centeredNav) {
					if (isNotBordering) {
						newPos = items[tempRel.centerItem].center;
					}
					if (forceCenteredNav && o.activateMiddle) {
						activate(tempRel.centerItem);
					}
				} else if (isNotBordering) {
					newPos = items[tempRel.firstItem].start;
				}
			}

			// Handle overflowing position limits
			if (dragging.init && dragging.slidee && o.elasticBounds) {
				if (newPos > pos.end) {
					newPos = pos.end + (newPos - pos.end) / 6;
				} else if (newPos < pos.start) {
					newPos = pos.start + (newPos - pos.start) / 6;
				}
			} else {
				newPos = within(newPos, pos.start, pos.end);
			}

			// Update the animation object
			animation.start = +new Date();
			animation.time = 0;
			animation.from = pos.cur;
			animation.to = newPos;
			animation.delta = newPos - pos.cur;
			animation.tweesing = dragging.tweese || dragging.init && !dragging.slidee;
			animation.immediate = !animation.tweesing && (immediate || dragging.init && dragging.slidee || !o.speed);

			// Reset dragging tweesing request
			dragging.tweese = 0;

			// Start animation rendering
			if (newPos !== pos.dest) {
				pos.dest = newPos;
				trigger('change');
				if (!renderID) {
					render();
				}
			}

			// Reset next cycle timeout
			resetCycle();

			// Synchronize states
			updateRelatives();
			updateButtonsState();
			syncPagesbar();
		}

		/**
		 * Render animation frame.
		 *
		 * @return {Void}
		 */
		function render() {
			if (!self.initialized) {
				return;
			}

			// If first render call, wait for next animationFrame
			if (!renderID) {
				renderID = rAF(render);
				if (dragging.released) {
					trigger('moveStart');
				}
				return;
			}

			// If immediate repositioning is requested, don't animate.
			if (animation.immediate) {
				pos.cur = animation.to;
			}
			// Use tweesing for animations without known end point
			else if (animation.tweesing) {
				animation.tweeseDelta = animation.to - pos.cur;
				// Fuck Zeno's paradox
				if (abs(animation.tweeseDelta) < 0.1) {
					pos.cur = animation.to;
				} else {
					pos.cur += animation.tweeseDelta * (dragging.released ? o.swingSpeed : o.syncSpeed);
				}
			}
			// Use tweening for basic animations with known end point
			else {
				animation.time = min(+new Date() - animation.start, o.speed);
				pos.cur = animation.from + animation.delta * $.easing[o.easing](animation.time/o.speed, animation.time, 0, 1, o.speed);
			}

			// If there is nothing more to render break the rendering loop, otherwise request new animation frame.
			if (animation.to === pos.cur) {
				pos.cur = animation.to;
				dragging.tweese = renderID = 0;
			} else {
				renderID = rAF(render);
			}

			trigger('move');

			// Update SLIDEE position
			if (!parallax) {
				if (transform) {
					$slidee[0].style[transform] = gpuAcceleration + (o.horizontal ? 'translateX' : 'translateY') + '(' + (-pos.cur) + 'px)';
				} else {
					$slidee[0].style[o.horizontal ? 'left' : 'top'] = -round(pos.cur) + 'px';
				}
			}

			// When animation reached the end, and dragging is not active, trigger moveEnd
			if (!renderID && dragging.released) {
				trigger('moveEnd');
			}

			syncScrollbar();
		}

		/**
		 * Synchronizes scrollbar with the SLIDEE.
		 *
		 * @return {Void}
		 */
		function syncScrollbar() {
			if ($handle.length) {
				hPos.cur = pos.start === pos.end ? 0 : (((dragging.init && !dragging.slidee) ? pos.dest : pos.cur) - pos.start) / (pos.end - pos.start) * hPos.end;
				hPos.cur = within(round(hPos.cur), hPos.start, hPos.end);
				if (last.hPos !== hPos.cur) {
					last.hPos = hPos.cur;
					if (transform) {
						$handle[0].style[transform] = gpuAcceleration + (o.horizontal ? 'translateX' : 'translateY') + '(' + hPos.cur + 'px)';
					} else {
						$handle[0].style[o.horizontal ? 'left' : 'top'] = hPos.cur + 'px';
					}
				}
			}
		}

		/**
		 * Synchronizes pagesbar with SLIDEE.
		 *
		 * @return {Void}
		 */
		function syncPagesbar() {
			if ($pages[0] && last.page !== rel.activePage) {
				last.page = rel.activePage;
				$pages.removeClass(o.activeClass).eq(rel.activePage).addClass(o.activeClass);
				trigger('activePage', last.page);
			}
		}

		/**
		 * Returns the position object.
		 *
		 * @param {Mixed} item
		 *
		 * @return {Object}
		 */
		self.getPos = function (item) {
			if (itemNav) {
				var index = getIndex(item);
				return index !== -1 ? items[index] : false;
			} else {
				var $item = $slidee.find(item).eq(0);

				if ($item[0]) {
					var offset = o.horizontal ? $item.offset().left - $slidee.offset().left : $item.offset().top - $slidee.offset().top;
					var size = $item[o.horizontal ? 'outerWidth' : 'outerHeight']();

					return {
						start: offset,
						center: offset - frameSize / 2 + size / 2,
						end: offset - frameSize + size,
						size: size
					};
				} else {
					return false;
				}
			}
		};

		/**
		 * Continuous move in a specified direction.
		 *
		 * @param  {Bool} forward True for forward movement, otherwise it'll go backwards.
		 * @param  {Int}  speed   Movement speed in pixels per frame. Overrides options.moveBy value.
		 *
		 * @return {Void}
		 */
		self.moveBy = function (speed) {
			move.speed = speed;
			// If already initiated, or there is nowhere to move, abort
			if (dragging.init || !move.speed || pos.cur === (move.speed > 0 ? pos.end : pos.start)) {
				return;
			}
			// Initiate move object
			move.lastTime = +new Date();
			move.startPos = pos.cur;
			// Set dragging as initiated
			continuousInit('button');
			dragging.init = 1;
			// Start movement
			trigger('moveStart');
			cAF(continuousID);
			moveLoop();
		};

		/**
		 * Continuous movement loop.
		 *
		 * @return {Void}
		 */
		function moveLoop() {
			// If there is nowhere to move anymore, stop
			if (!move.speed || pos.cur === (move.speed > 0 ? pos.end : pos.start)) {
				self.stop();
			}
			// Request new move loop if it hasn't been stopped
			continuousID = dragging.init ? rAF(moveLoop) : 0;
			// Update move object
			move.now = +new Date();
			move.pos = pos.cur + (move.now - move.lastTime) / 1000 * move.speed;
			// Slide
			slideTo(dragging.init ? move.pos : round(move.pos));
			// Normally, this is triggered in render(), but if there
			// is nothing to render, we have to do it manually here.
			if (!dragging.init && pos.cur === pos.dest) {
				trigger('moveEnd');
			}
			// Update times for future iteration
			move.lastTime = move.now;
		}

		/**
		 * Stops continuous movement.
		 *
		 * @return {Void}
		 */
		self.stop = function () {
			if (dragging.source === 'button') {
				dragging.init = 0;
				dragging.released = 1;
			}
		};

		/**
		 * Activate previous item.
		 *
		 * @return {Void}
		 */
		self.prev = function () {
			self.activate(rel.activeItem == null ? 0 : rel.activeItem - 1);
		};

		/**
		 * Activate next item.
		 *
		 * @return {Void}
		 */
		self.next = function () {
			self.activate(rel.activeItem == null ? 0 : rel.activeItem + 1);
		};

		/**
		 * Activate previous page.
		 *
		 * @return {Void}
		 */
		self.prevPage = function () {
			self.activatePage(rel.activePage - 1);
		};

		/**
		 * Activate next page.
		 *
		 * @return {Void}
		 */
		self.nextPage = function () {
			self.activatePage(rel.activePage + 1);
		};

		/**
		 * Slide SLIDEE by amount of pixels.
		 *
		 * @param {Int}  delta     Pixels/Items. Positive means forward, negative means backward.
		 * @param {Bool} immediate Reposition immediately without an animation.
		 *
		 * @return {Void}
		 */
		self.slideBy = function (delta, immediate) {
			if (!delta) {
				return;
			}
			if (itemNav) {
				self[centeredNav ? 'toCenter' : 'toStart'](
					within((centeredNav ? rel.centerItem : rel.firstItem) + o.scrollBy * delta, 0, items.length)
				);
			} else {
				slideTo(pos.dest + delta, immediate);
			}
		};

		/**
		 * Animate SLIDEE to a specific position.
		 *
		 * @param {Int}  pos       New position.
		 * @param {Bool} immediate Reposition immediately without an animation.
		 *
		 * @return {Void}
		 */
		self.slideTo = function (pos, immediate) {
			slideTo(pos, immediate);
		};

		/**
		 * Core method for handling `toLocation` methods.
		 *
		 * @param  {String} location
		 * @param  {Mixed}  item
		 * @param  {Bool}   immediate
		 *
		 * @return {Void}
		 */
		function to(location, item, immediate) {
			// Optional arguments logic
			if (type(item) === 'boolean') {
				immediate = item;
				item = undefined;
			}

			if (item === undefined) {
				slideTo(pos[location], immediate);
			} else {
				// You can't align items to sides of the frame
				// when centered navigation type is enabled
				if (centeredNav && location !== 'center') {
					return;
				}

				var itemPos = self.getPos(item);
				if (itemPos) {
					slideTo(itemPos[location], immediate, !centeredNav);
				}
			}
		}

		/**
		 * Animate element or the whole SLIDEE to the start of the frame.
		 *
		 * @param {Mixed} item      Item DOM element, or index starting at 0. Omitting will animate SLIDEE.
		 * @param {Bool}  immediate Reposition immediately without an animation.
		 *
		 * @return {Void}
		 */
		self.toStart = function (item, immediate) {
			to('start', item, immediate);
		};

		/**
		 * Animate element or the whole SLIDEE to the end of the frame.
		 *
		 * @param {Mixed} item      Item DOM element, or index starting at 0. Omitting will animate SLIDEE.
		 * @param {Bool}  immediate Reposition immediately without an animation.
		 *
		 * @return {Void}
		 */
		self.toEnd = function (item, immediate) {
			to('end', item, immediate);
		};

		/**
		 * Animate element or the whole SLIDEE to the center of the frame.
		 *
		 * @param {Mixed} item      Item DOM element, or index starting at 0. Omitting will animate SLIDEE.
		 * @param {Bool}  immediate Reposition immediately without an animation.
		 *
		 * @return {Void}
		 */
		self.toCenter = function (item, immediate) {
			to('center', item, immediate);
		};

		/**
		 * Get the index of an item in SLIDEE.
		 *
		 * @param {Mixed} item     Item DOM element.
		 *
		 * @return {Int}  Item index, or -1 if not found.
		 */
		function getIndex(item) {
			return item != null ?
					isNumber(item) ?
						item >= 0 && item < items.length ? item : -1 :
						$items.index(item) :
					-1;
		}
		// Expose getIndex without lowering the compressibility of it,
		// as it is used quite often throughout Sly.
		self.getIndex = getIndex;

		/**
		 * Get index of an item in SLIDEE based on a variety of input types.
		 *
		 * @param  {Mixed} item DOM element, positive or negative integer.
		 *
		 * @return {Int}   Item index, or -1 if not found.
		 */
		function getRelativeIndex(item) {
			return getIndex(isNumber(item) && item < 0 ? item + items.length : item);
		}

		/**
		 * Activates an item.
		 *
		 * @param  {Mixed} item Item DOM element, or index starting at 0.
		 *
		 * @return {Mixed} Activated item index or false on fail.
		 */
		function activate(item, force) {
			var index = getIndex(item);

			if (!itemNav || index < 0) {
				return false;
			}

			// Update classes, last active index, and trigger active event only when there
			// has been a change. Otherwise just return the current active index.
			if (last.active !== index || force) {
				// Update classes
				$items.eq(rel.activeItem).removeClass(o.activeClass);
				$items.eq(index).addClass(o.activeClass);

				last.active = rel.activeItem = index;

				updateButtonsState();
				trigger('active', index);
			}

			return index;
		}

		/**
		 * Activates an item and helps with further navigation when o.smart is enabled.
		 *
		 * @param {Mixed} item      Item DOM element, or index starting at 0.
		 * @param {Bool}  immediate Whether to reposition immediately in smart navigation.
		 *
		 * @return {Void}
		 */
		self.activate = function (item, immediate) {
			var index = activate(item);

			// Smart navigation
			if (o.smart && index !== false) {
				// When centeredNav is enabled, center the element.
				// Otherwise, determine where to position the element based on its current position.
				// If the element is currently on the far end side of the frame, assume that user is
				// moving forward and animate it to the start of the visible frame, and vice versa.
				if (centeredNav) {
					self.toCenter(index, immediate);
				} else if (index >= rel.lastItem) {
					self.toStart(index, immediate);
				} else if (index <= rel.firstItem) {
					self.toEnd(index, immediate);
				} else {
					resetCycle();
				}
			}
		};

		/**
		 * Activates a page.
		 *
		 * @param {Int}  index     Page index, starting from 0.
		 * @param {Bool} immediate Whether to reposition immediately without animation.
		 *
		 * @return {Void}
		 */
		self.activatePage = function (index, immediate) {
			if (isNumber(index)) {
				slideTo(pages[within(index, 0, pages.length - 1)], immediate);
			}
		};

		/**
		 * Return relative positions of items based on their visibility within FRAME.
		 *
		 * @param {Int} slideePos Position of SLIDEE.
		 *
		 * @return {Void}
		 */
		function getRelatives(slideePos) {
			slideePos = within(isNumber(slideePos) ? slideePos : pos.dest, pos.start, pos.end);

			var relatives = {};
			var centerOffset = forceCenteredNav ? 0 : frameSize / 2;

			// Determine active page
			if (!parallax) {
				for (var p = 0, pl = pages.length; p < pl; p++) {
					if (slideePos >= pos.end || p === pages.length - 1) {
						relatives.activePage = pages.length - 1;
						break;
					}

					if (slideePos <= pages[p] + centerOffset) {
						relatives.activePage = p;
						break;
					}
				}
			}

			// Relative item indexes
			if (itemNav) {
				var first = false;
				var last = false;
				var center = false;

				// From start
				for (var i = 0, il = items.length; i < il; i++) {
					// First item
					if (first === false && slideePos <= items[i].start + items[i].half) {
						first = i;
					}

					// Center item
					if (center === false && slideePos <= items[i].center + items[i].half) {
						center = i;
					}

					// Last item
					if (i === il - 1 || slideePos <= items[i].end + items[i].half) {
						last = i;
						break;
					}
				}

				// Safe assignment, just to be sure the false won't be returned
				relatives.firstItem = isNumber(first) ? first : 0;
				relatives.centerItem = isNumber(center) ? center : relatives.firstItem;
				relatives.lastItem = isNumber(last) ? last : relatives.centerItem;
			}

			return relatives;
		}

		/**
		 * Update object with relative positions.
		 *
		 * @param {Int} newPos
		 *
		 * @return {Void}
		 */
		function updateRelatives(newPos) {
			$.extend(rel, getRelatives(newPos));
		}

		/**
		 * Disable navigation buttons when needed.
		 *
		 * Adds disabledClass, and when the button is <button> or <input>, activates :disabled state.
		 *
		 * @return {Void}
		 */
		function updateButtonsState() {
			var isStart = pos.dest <= pos.start;
			var isEnd = pos.dest >= pos.end;
			var slideePosState = (isStart ? 1 : 0) | (isEnd ? 2 : 0);

			// Update paging buttons only if there has been a change in SLIDEE position
			if (last.slideePosState !== slideePosState) {
				last.slideePosState = slideePosState;

				if ($prevPageButton.is('button,input')) {
					$prevPageButton.prop('disabled', isStart);
				}

				if ($nextPageButton.is('button,input')) {
					$nextPageButton.prop('disabled', isEnd);
				}

				$prevPageButton.add($backwardButton)[isStart ? 'addClass' : 'removeClass'](o.disabledClass);
				$nextPageButton.add($forwardButton)[isEnd ? 'addClass' : 'removeClass'](o.disabledClass);
			}

			// Forward & Backward buttons need a separate state caching because we cannot "property disable"
			// them while they are being used, as disabled buttons stop emitting mouse events.
			if (last.fwdbwdState !== slideePosState && dragging.released) {
				last.fwdbwdState = slideePosState;

				if ($backwardButton.is('button,input')) {
					$backwardButton.prop('disabled', isStart);
				}

				if ($forwardButton.is('button,input')) {
					$forwardButton.prop('disabled', isEnd);
				}
			}

			// Item navigation
			if (itemNav && rel.activeItem != null) {
				var isFirst = rel.activeItem === 0;
				var isLast = rel.activeItem >= items.length - 1;
				var itemsButtonState = (isFirst ? 1 : 0) | (isLast ? 2 : 0);

				if (last.itemsButtonState !== itemsButtonState) {
					last.itemsButtonState = itemsButtonState;

					if ($prevButton.is('button,input')) {
						$prevButton.prop('disabled', isFirst);
					}

					if ($nextButton.is('button,input')) {
						$nextButton.prop('disabled', isLast);
					}

					$prevButton[isFirst ? 'addClass' : 'removeClass'](o.disabledClass);
					$nextButton[isLast ? 'addClass' : 'removeClass'](o.disabledClass);
				}
			}
		}

		/**
		 * Resume cycling.
		 *
		 * @param {Int} priority Resume pause with priority lower or equal than this. Used internally for pauseOnHover.
		 *
		 * @return {Void}
		 */
		self.resume = function (priority) {
			if (!o.cycleBy || !o.cycleInterval || o.cycleBy === 'items' && (!items[0] || rel.activeItem == null) || priority < self.isPaused) {
				return;
			}

			self.isPaused = 0;

			if (cycleID) {
				cycleID = clearTimeout(cycleID);
			} else {
				trigger('resume');
			}

			cycleID = setTimeout(function () {
				trigger('cycle');
				switch (o.cycleBy) {
					case 'items':
						self.activate(rel.activeItem >= items.length - 1 ? 0 : rel.activeItem + 1);
						break;

					case 'pages':
						self.activatePage(rel.activePage >= pages.length - 1 ? 0 : rel.activePage + 1);
						break;
				}
			}, o.cycleInterval);
		};

		/**
		 * Pause cycling.
		 *
		 * @param {Int} priority Pause priority. 100 is default. Used internally for pauseOnHover.
		 *
		 * @return {Void}
		 */
		self.pause = function (priority) {
			if (priority < self.isPaused) {
				return;
			}

			self.isPaused = priority || 100;

			if (cycleID) {
				cycleID = clearTimeout(cycleID);
				trigger('pause');
			}
		};

		/**
		 * Toggle cycling.
		 *
		 * @return {Void}
		 */
		self.toggle = function () {
			self[cycleID ? 'pause' : 'resume']();
		};

		/**
		 * Updates a signle or multiple option values.
		 *
		 * @param {Mixed} name  Name of the option that should be updated, or object that will extend the options.
		 * @param {Mixed} value New option value.
		 *
		 * @return {Void}
		 */
		self.set = function (name, value) {
			if ($.isPlainObject(name)) {
				$.extend(o, name);
			} else if (o.hasOwnProperty(name)) {
				o[name] = value;
			}
		};

		/**
		 * Add one or multiple items to the SLIDEE end, or a specified position index.
		 *
		 * @param {Mixed} element Node element, or HTML string.
		 * @param {Int}   index   Index of a new item position. By default item is appended at the end.
		 *
		 * @return {Void}
		 */
		self.add = function (element, index) {
			var $element = $(element);

			if (itemNav) {
				// Insert the element(s)
				if (index == null || !items[0] || index >= items.length) {
					$element.appendTo($slidee);
				} else if (items.length) {
					$element.insertBefore(items[index].el);
				}

				// Adjust the activeItem index
				if (rel.activeItem != null && index <= rel.activeItem) {
					last.active = rel.activeItem += $element.length;
				}
			} else {
				$slidee.append($element);
			}

			// Reload
			load();
		};

		/**
		 * Remove an item from SLIDEE.
		 *
		 * @param {Mixed} element Item index, or DOM element.
		 * @param {Int}   index   Index of a new item position. By default item is appended at the end.
		 *
		 * @return {Void}
		 */
		self.remove = function (element) {
			if (itemNav) {
				var index = getRelativeIndex(element);

				if (index > -1) {
					// Remove the element
					$items.eq(index).remove();

					// If the current item is being removed, activate new one after reload
					var reactivate = index === rel.activeItem;

					// Adjust the activeItem index
					if (rel.activeItem != null && index < rel.activeItem) {
						last.active = --rel.activeItem;
					}

					// Reload
					load();

					// Activate new item at the removed position
					if (reactivate) {
						last.active = null;
						self.activate(rel.activeItem);
					}
				}
			} else {
				$(element).remove();
				load();
			}
		};

		/**
		 * Helps re-arranging items.
		 *
		 * @param  {Mixed} item     Item DOM element, or index starting at 0. Use negative numbers to select items from the end.
		 * @param  {Mixed} position Item insertion anchor. Accepts same input types as item argument.
		 * @param  {Bool}  after    Insert after instead of before the anchor.
		 *
		 * @return {Void}
		 */
		function moveItem(item, position, after) {
			item = getRelativeIndex(item);
			position = getRelativeIndex(position);

			// Move only if there is an actual change requested
			if (item > -1 && position > -1 && item !== position && (!after || position !== item - 1) && (after || position !== item + 1)) {
				$items.eq(item)[after ? 'insertAfter' : 'insertBefore'](items[position].el);

				var shiftStart = item < position ? item : (after ? position : position - 1);
				var shiftEnd = item > position ? item : (after ? position + 1 : position);
				var shiftsUp = item > position;

				// Update activeItem index
				if (rel.activeItem != null) {
					if (item === rel.activeItem) {
						last.active = rel.activeItem = after ? (shiftsUp ? position + 1 : position) : (shiftsUp ? position : position - 1);
					} else if (rel.activeItem > shiftStart && rel.activeItem < shiftEnd) {
						last.active = rel.activeItem += shiftsUp ? 1 : -1;
					}
				}

				// Reload
				load();
			}
		}

		/**
		 * Move item after the target anchor.
		 *
		 * @param  {Mixed} item     Item to be moved. Can be DOM element or item index.
		 * @param  {Mixed} position Target position anchor. Can be DOM element or item index.
		 *
		 * @return {Void}
		 */
		self.moveAfter = function (item, position) {
			moveItem(item, position, 1);
		};

		/**
		 * Move item before the target anchor.
		 *
		 * @param  {Mixed} item     Item to be moved. Can be DOM element or item index.
		 * @param  {Mixed} position Target position anchor. Can be DOM element or item index.
		 *
		 * @return {Void}
		 */
		self.moveBefore = function (item, position) {
			moveItem(item, position);
		};

		/**
		 * Registers callbacks.
		 *
		 * @param  {Mixed} name  Event name, or callbacks map.
		 * @param  {Mixed} fn    Callback, or an array of callback functions.
		 *
		 * @return {Void}
		 */
		self.on = function (name, fn) {
			// Callbacks map
			if (type(name) === 'object') {
				for (var key in name) {
					if (name.hasOwnProperty(key)) {
						self.on(key, name[key]);
					}
				}
			// Callback
			} else if (type(fn) === 'function') {
				var names = name.split(' ');
				for (var n = 0, nl = names.length; n < nl; n++) {
					callbacks[names[n]] = callbacks[names[n]] || [];
					if (callbackIndex(names[n], fn) === -1) {
						callbacks[names[n]].push(fn);
					}
				}
			// Callbacks array
			} else if (type(fn) === 'array') {
				for (var f = 0, fl = fn.length; f < fl; f++) {
					self.on(name, fn[f]);
				}
			}
		};

		/**
		 * Registers callbacks to be executed only once.
		 *
		 * @param  {Mixed} name  Event name, or callbacks map.
		 * @param  {Mixed} fn    Callback, or an array of callback functions.
		 *
		 * @return {Void}
		 */
		self.one = function (name, fn) {
			function proxy() {
				fn.apply(self, arguments);
				self.off(name, proxy);
			}
			self.on(name, proxy);
		};

		/**
		 * Remove one or all callbacks.
		 *
		 * @param  {String} name Event name.
		 * @param  {Mixed}  fn   Callback, or an array of callback functions. Omit to remove all callbacks.
		 *
		 * @return {Void}
		 */
		self.off = function (name, fn) {
			if (fn instanceof Array) {
				for (var f = 0, fl = fn.length; f < fl; f++) {
					self.off(name, fn[f]);
				}
			} else {
				var names = name.split(' ');
				for (var n = 0, nl = names.length; n < nl; n++) {
					callbacks[names[n]] = callbacks[names[n]] || [];
					if (fn == null) {
						callbacks[names[n]].length = 0;
					} else {
						var index = callbackIndex(names[n], fn);
						if (index !== -1) {
							callbacks[names[n]].splice(index, 1);
						}
					}
				}
			}
		};

		/**
		 * Returns callback array index.
		 *
		 * @param  {String}   name Event name.
		 * @param  {Function} fn   Function
		 *
		 * @return {Int} Callback array index, or -1 if isn't registered.
		 */
		function callbackIndex(name, fn) {
			for (var i = 0, l = callbacks[name].length; i < l; i++) {
				if (callbacks[name][i] === fn) {
					return i;
				}
			}
			return -1;
		}

		/**
		 * Reset next cycle timeout.
		 *
		 * @return {Void}
		 */
		function resetCycle() {
			if (dragging.released && !self.isPaused) {
				self.resume();
			}
		}

		/**
		 * Calculate SLIDEE representation of handle position.
		 *
		 * @param  {Int} handlePos
		 *
		 * @return {Int}
		 */
		function handleToSlidee(handlePos) {
			return round(within(handlePos, hPos.start, hPos.end) / hPos.end * (pos.end - pos.start)) + pos.start;
		}

		/**
		 * Keeps track of a dragging delta history.
		 *
		 * @return {Void}
		 */
		function draggingHistoryTick() {
			// Looking at this, I know what you're thinking :) But as we need only 4 history states, doing it this way
			// as opposed to a proper loop is ~25 bytes smaller (when minified with GCC), a lot faster, and doesn't
			// generate garbage. The loop version would create 2 new variables on every tick. Unexaptable!
			dragging.history[0] = dragging.history[1];
			dragging.history[1] = dragging.history[2];
			dragging.history[2] = dragging.history[3];
			dragging.history[3] = dragging.delta;
		}

		/**
		 * Initialize continuous movement.
		 *
		 * @return {Void}
		 */
		function continuousInit(source) {
			dragging.released = 0;
			dragging.source = source;
			dragging.slidee = source === 'slidee';
		}

		/**
		 * Dragging initiator.
		 *
		 * @param  {Event} event
		 *
		 * @return {Void}
		 */
		function dragInit(event) {
			var isTouch = event.type === 'touchstart';
			var source = event.data.source;
			var isSlidee = source === 'slidee';

			// Ignore when already in progress, or interactive element in non-touch navivagion
			if (dragging.init || !isTouch && isInteractive(event.target)) {
				return;
			}

			// Handle dragging conditions
			if (source === 'handle' && (!o.dragHandle || hPos.start === hPos.end)) {
				return;
			}

			// SLIDEE dragging conditions
			if (isSlidee && !(isTouch ? o.touchDragging : o.mouseDragging && event.which < 2)) {
				return;
			}

			if (!isTouch) {
				// prevents native image dragging in Firefox
				stopDefault(event);
			}

			// Reset dragging object
			continuousInit(source);

			// Properties used in dragHandler
			dragging.init = 0;
			dragging.$source = $(event.target);
			dragging.touch = isTouch;
			dragging.pointer = isTouch ? event.originalEvent.touches[0] : event;
			dragging.initX = dragging.pointer.pageX;
			dragging.initY = dragging.pointer.pageY;
			dragging.initPos = isSlidee ? pos.cur : hPos.cur;
			dragging.start = +new Date();
			dragging.time = 0;
			dragging.path = 0;
			dragging.delta = 0;
			dragging.locked = 0;
			dragging.history = [0, 0, 0, 0];
			dragging.pathToLock = isSlidee ? isTouch ? 30 : 10 : 0;

			// Bind dragging events
			$doc.on(isTouch ? dragTouchEvents : dragMouseEvents, dragHandler);

			// Pause ongoing cycle
			self.pause(1);

			// Add dragging class
			(isSlidee ? $slidee : $handle).addClass(o.draggedClass);

			// Trigger moveStart event
			trigger('moveStart');

			// Keep track of a dragging path history. This is later used in the
			// dragging release swing calculation when dragging SLIDEE.
			if (isSlidee) {
				historyID = setInterval(draggingHistoryTick, 10);
			}
		}

		/**
		 * Handler for dragging scrollbar handle or SLIDEE.
		 *
		 * @param  {Event} event
		 *
		 * @return {Void}
		 */
		function dragHandler(event) {
			dragging.released = event.type === 'mouseup' || event.type === 'touchend';
			dragging.pointer = dragging.touch ? event.originalEvent[dragging.released ? 'changedTouches' : 'touches'][0] : event;
			dragging.pathX = dragging.pointer.pageX - dragging.initX;
			dragging.pathY = dragging.pointer.pageY - dragging.initY;
			dragging.path = sqrt(pow(dragging.pathX, 2) + pow(dragging.pathY, 2));
			dragging.delta = o.horizontal ? dragging.pathX : dragging.pathY;

			if (!dragging.released && dragging.path < 1) return;

			// We haven't decided whether this is a drag or not...
			if (!dragging.init) {
				// If the drag path was very short, maybe it's not a drag?
				if (dragging.path < o.dragThreshold) {
					// If the pointer was released, the path will not become longer and it's
					// definitely not a drag. If not released yet, decide on next iteration
					return dragging.released ? dragEnd() : undefined;
				}
				else {
					// If dragging path is sufficiently long we can confidently start a drag
					// if drag is in different direction than scroll, ignore it
					if (o.horizontal ? abs(dragging.pathX) > abs(dragging.pathY) : abs(dragging.pathX) < abs(dragging.pathY)) {
						dragging.init = 1;
					} else {
						return dragEnd();
					}
				}
			}

			stopDefault(event);

			// Disable click on a source element, as it is unwelcome when dragging
			if (!dragging.locked && dragging.path > dragging.pathToLock && dragging.slidee) {
				dragging.locked = 1;
				dragging.$source.on(clickEvent, disableOneEvent);
			}

			// Cancel dragging on release
			if (dragging.released) {
				dragEnd();

				// Adjust path with a swing on mouse release
				if (o.releaseSwing && dragging.slidee) {
					dragging.swing = (dragging.delta - dragging.history[0]) / 40 * 300;
					dragging.delta += dragging.swing;
					dragging.tweese = abs(dragging.swing) > 10;
				}
			}

			slideTo(dragging.slidee ? round(dragging.initPos - dragging.delta) : handleToSlidee(dragging.initPos + dragging.delta));
		}

		/**
		 * Stops dragging and cleans up after it.
		 *
		 * @return {Void}
		 */
		function dragEnd() {
			clearInterval(historyID);
			dragging.released = true;
			$doc.off(dragging.touch ? dragTouchEvents : dragMouseEvents, dragHandler);
			(dragging.slidee ? $slidee : $handle).removeClass(o.draggedClass);

			// Make sure that disableOneEvent is not active in next tick.
			setTimeout(function () {
				dragging.$source.off(clickEvent, disableOneEvent);
			});

			// Normally, this is triggered in render(), but if there
			// is nothing to render, we have to do it manually here.
			if (pos.cur === pos.dest && dragging.init) {
				trigger('moveEnd');
			}

			// Resume ongoing cycle
			self.resume(1);

			dragging.init = 0;
		}

		/**
		 * Check whether element is interactive.
		 *
		 * @return {Boolean}
		 */
		function isInteractive(element) {
			return ~$.inArray(element.nodeName, interactiveElements) || $(element).is(o.interactive);
		}

		/**
		 * Continuous movement cleanup on mouseup.
		 *
		 * @return {Void}
		 */
		function movementReleaseHandler() {
			self.stop();
			$doc.off('mouseup', movementReleaseHandler);
		}

		/**
		 * Buttons navigation handler.
		 *
		 * @param  {Event} event
		 *
		 * @return {Void}
		 */
		function buttonsHandler(event) {
			/*jshint validthis:true */
			stopDefault(event);
			switch (this) {
				case $forwardButton[0]:
				case $backwardButton[0]:
					self.moveBy($forwardButton.is(this) ? o.moveBy : -o.moveBy);
					$doc.on('mouseup', movementReleaseHandler);
					break;

				case $prevButton[0]:
					self.prev();
					break;

				case $nextButton[0]:
					self.next();
					break;

				case $prevPageButton[0]:
					self.prevPage();
					break;

				case $nextPageButton[0]:
					self.nextPage();
					break;
			}
		}

		/**
		 * Mouse wheel delta normalization.
		 *
		 * @param  {Event} event
		 *
		 * @return {Int}
		 */
		function normalizeWheelDelta(event) {
			// wheelDelta needed only for IE8-
			scrolling.curDelta = ((o.horizontal ? event.deltaY || event.deltaX : event.deltaY) || -event.wheelDelta);
			scrolling.curDelta /= event.deltaMode === 1 ? 3 : 100;
			if (!itemNav) {
				return scrolling.curDelta;
			}
			time = +new Date();
			if (scrolling.last < time - scrolling.resetTime) {
				scrolling.delta = 0;
			}
			scrolling.last = time;
			scrolling.delta += scrolling.curDelta;
			if (abs(scrolling.delta) < 1) {
				scrolling.finalDelta = 0;
			} else {
				scrolling.finalDelta = round(scrolling.delta / 1);
				scrolling.delta %= 1;
			}
			return scrolling.finalDelta;
		}

		/**
		 * Mouse scrolling handler.
		 *
		 * @param  {Event} event
		 *
		 * @return {Void}
		 */
		function scrollHandler(event) {
			// Mark event as originating in a Sly instance
			event.originalEvent[namespace] = self;
			// Don't hijack global scrolling
			var time = +new Date();
			if (lastGlobalWheel + o.scrollHijack > time && $scrollSource[0] !== document && $scrollSource[0] !== window) {
				lastGlobalWheel = time;
				return;
			}
			// Ignore if there is no scrolling to be done
			if (!o.scrollBy || pos.start === pos.end) {
				return;
			}
			var delta = normalizeWheelDelta(event.originalEvent);
			// Trap scrolling only when necessary and/or requested
			if (o.scrollTrap || delta > 0 && pos.dest < pos.end || delta < 0 && pos.dest > pos.start) {
				stopDefault(event, 1);
			}
			self.slideBy(o.scrollBy * delta);
		}

		/**
		 * Scrollbar click handler.
		 *
		 * @param  {Event} event
		 *
		 * @return {Void}
		 */
		function scrollbarHandler(event) {
			// Only clicks on scroll bar. Ignore the handle.
			if (o.clickBar && event.target === $sb[0]) {
				stopDefault(event);
				// Calculate new handle position and sync SLIDEE to it
				slideTo(handleToSlidee((o.horizontal ? event.pageX - $sb.offset().left : event.pageY - $sb.offset().top) - handleSize / 2));
			}
		}

		/**
		 * Keyboard input handler.
		 *
		 * @param  {Event} event
		 *
		 * @return {Void}
		 */
		function keyboardHandler(event) {
			if (!o.keyboardNavBy) {
				return;
			}

			switch (event.which) {
				// Left or Up
				case o.horizontal ? 37 : 38:
					stopDefault(event);
					self[o.keyboardNavBy === 'pages' ? 'prevPage' : 'prev']();
					break;

				// Right or Down
				case o.horizontal ? 39 : 40:
					stopDefault(event);
					self[o.keyboardNavBy === 'pages' ? 'nextPage' : 'next']();
					break;
			}
		}

		/**
		 * Click on item activation handler.
		 *
		 * @param  {Event} event
		 *
		 * @return {Void}
		 */
		function activateHandler(event) {
			/*jshint validthis:true */

			// Ignore clicks on interactive elements.
			if (isInteractive(this)) {
				event.originalEvent[namespace + 'ignore'] = true;
				return;
			}

			// Ignore events that:
			// - are not originating from direct SLIDEE children
			// - originated from interactive elements
			if (this.parentNode !== $slidee[0] || event.originalEvent[namespace + 'ignore']) return;

			self.activate(this);
		}

		/**
		 * Click on page button handler.
		 *
		 * @param {Event} event
		 *
		 * @return {Void}
		 */
		function activatePageHandler() {
			/*jshint validthis:true */
			// Accept only events from direct pages bar children.
			if (this.parentNode === $pb[0]) {
				self.activatePage($pages.index(this));
			}
		}

		/**
		 * Pause on hover handler.
		 *
		 * @param  {Event} event
		 *
		 * @return {Void}
		 */
		function pauseOnHoverHandler(event) {
			if (o.pauseOnHover) {
				self[event.type === 'mouseenter' ? 'pause' : 'resume'](2);
			}
		}

		/**
		 * Trigger callbacks for event.
		 *
		 * @param  {String} name Event name.
		 * @param  {Mixed}  argX Arguments passed to callbacks.
		 *
		 * @return {Void}
		 */
		function trigger(name, arg1) {
			if (callbacks[name]) {
				l = callbacks[name].length;
				// Callbacks will be stored and executed from a temporary array to not
				// break the execution queue when one of the callbacks unbinds itself.
				tmpArray.length = 0;
				for (i = 0; i < l; i++) {
					tmpArray.push(callbacks[name][i]);
				}
				// Execute the callbacks
				for (i = 0; i < l; i++) {
					tmpArray[i].call(self, name, arg1);
				}
			}
		}

		/**
		 * Destroys instance and everything it created.
		 *
		 * @return {Void}
		 */
		self.destroy = function () {
			// Unbind all events
			$scrollSource
				.add($handle)
				.add($sb)
				.add($pb)
				.add($forwardButton)
				.add($backwardButton)
				.add($prevButton)
				.add($nextButton)
				.add($prevPageButton)
				.add($nextPageButton)
				.off('.' + namespace);

			// Unbinding specifically as to not nuke out other instances
			$doc.off('keydown', keyboardHandler);

			// Remove classes
			$prevButton
				.add($nextButton)
				.add($prevPageButton)
				.add($nextPageButton)
				.removeClass(o.disabledClass);

			if ($items && rel.activeItem != null) {
				$items.eq(rel.activeItem).removeClass(o.activeClass);
			}

			// Remove page items
			$pb.empty();

			if (!parallax) {
				// Unbind events from frame
				$frame.off('.' + namespace);
				// Restore original styles
				frameStyles.restore();
				slideeStyles.restore();
				sbStyles.restore();
				handleStyles.restore();
				// Remove the instance from element data storage
				$.removeData(frame, namespace);
			}

			// Clean up collections
			items.length = pages.length = 0;
			last = {};

			// Reset initialized status and return the instance
			self.initialized = 0;
			return self;
		};

		/**
		 * Initialize.
		 *
		 * @return {Object}
		 */
		self.init = function () {
			if (self.initialized) {
				return;
			}

			// Register callbacks map
			self.on(callbackMap);

			// Save styles
			var holderProps = ['overflow', 'position'];
			var movableProps = ['position', 'webkitTransform', 'msTransform', 'transform', 'left', 'top', 'width', 'height'];
			frameStyles.save.apply(frameStyles, holderProps);
			sbStyles.save.apply(sbStyles, holderProps);
			slideeStyles.save.apply(slideeStyles, movableProps);
			handleStyles.save.apply(handleStyles, movableProps);

			// Set required styles
			var $movables = $handle;
			if (!parallax) {
				$movables = $movables.add($slidee);
				$frame.css('overflow', 'hidden');
				if (!transform && $frame.css('position') === 'static') {
					$frame.css('position', 'relative');
				}
			}
			if (transform) {
				if (gpuAcceleration) {
					$movables.css(transform, gpuAcceleration);
				}
			} else {
				if ($sb.css('position') === 'static') {
					$sb.css('position', 'relative');
				}
				$movables.css({ position: 'absolute' });
			}

			// Navigation buttons
			if (o.forward) {
				$forwardButton.on(mouseDownEvent, buttonsHandler);
			}
			if (o.backward) {
				$backwardButton.on(mouseDownEvent, buttonsHandler);
			}
			if (o.prev) {
				$prevButton.on(clickEvent, buttonsHandler);
			}
			if (o.next) {
				$nextButton.on(clickEvent, buttonsHandler);
			}
			if (o.prevPage) {
				$prevPageButton.on(clickEvent, buttonsHandler);
			}
			if (o.nextPage) {
				$nextPageButton.on(clickEvent, buttonsHandler);
			}

			// Scrolling navigation
			$scrollSource.on(wheelEvent, scrollHandler);

			// Clicking on scrollbar navigation
			if ($sb[0]) {
				$sb.on(clickEvent, scrollbarHandler);
			}

			// Click on items navigation
			if (itemNav && o.activateOn) {
				$frame.on(o.activateOn + '.' + namespace, '*', activateHandler);
			}

			// Pages navigation
			if ($pb[0] && o.activatePageOn) {
				$pb.on(o.activatePageOn + '.' + namespace, '*', activatePageHandler);
			}

			// Dragging navigation
			$dragSource.on(dragInitEvents, { source: 'slidee' }, dragInit);

			// Scrollbar dragging navigation
			if ($handle) {
				$handle.on(dragInitEvents, { source: 'handle' }, dragInit);
			}

			// Keyboard navigation
			$doc.on('keydown', keyboardHandler);

			if (!parallax) {
				// Pause on hover
				$frame.on('mouseenter.' + namespace + ' mouseleave.' + namespace, pauseOnHoverHandler);
				// Reset native FRAME element scroll
				$frame.on('scroll.' + namespace, resetScroll);
			}

			// Mark instance as initialized
			self.initialized = 1;

			// Load
			load(true);

			// Initiate automatic cycling
			if (o.cycleBy && !parallax) {
				self[o.startPaused ? 'pause' : 'resume']();
			}

			// Return instance
			return self;
		};
	}

	/**
	 * Return type of the value.
	 *
	 * @param  {Mixed} value
	 *
	 * @return {String}
	 */
	function type(value) {
		if (value == null) {
			return String(value);
		}

		if (typeof value === 'object' || typeof value === 'function') {
			return Object.prototype.toString.call(value).match(/\s([a-z]+)/i)[1].toLowerCase() || 'object';
		}

		return typeof value;
	}

	/**
	 * Event preventDefault & stopPropagation helper.
	 *
	 * @param {Event} event     Event object.
	 * @param {Bool}  noBubbles Cancel event bubbling.
	 *
	 * @return {Void}
	 */
	function stopDefault(event, noBubbles) {
		event.preventDefault();
		if (noBubbles) {
			event.stopPropagation();
		}
	}

	/**
	 * Disables an event it was triggered on and unbinds itself.
	 *
	 * @param  {Event} event
	 *
	 * @return {Void}
	 */
	function disableOneEvent(event) {
		/*jshint validthis:true */
		stopDefault(event, 1);
		$(this).off(event.type, disableOneEvent);
	}

	/**
	 * Resets native element scroll values to 0.
	 *
	 * @return {Void}
	 */
	function resetScroll() {
		/*jshint validthis:true */
		this.scrollLeft = 0;
		this.scrollTop = 0;
	}

	/**
	 * Check if variable is a number.
	 *
	 * @param {Mixed} value
	 *
	 * @return {Boolean}
	 */
	function isNumber(value) {
		return !isNaN(parseFloat(value)) && isFinite(value);
	}

	/**
	 * Parse style to pixels.
	 *
	 * @param {Object}   $item    jQuery object with element.
	 * @param {Property} property CSS property to get the pixels from.
	 *
	 * @return {Int}
	 */
	function getPx($item, property) {
		return 0 | round(String($item.css(property)).replace(/[^\-0-9.]/g, ''));
	}

	/**
	 * Make sure that number is within the limits.
	 *
	 * @param {Number} number
	 * @param {Number} min
	 * @param {Number} max
	 *
	 * @return {Number}
	 */
	function within(number, min, max) {
		return number < min ? min : number > max ? max : number;
	}

	/**
	 * Saves element styles for later restoration.
	 *
	 * Example:
	 *   var styles = new StyleRestorer(frame);
	 *   styles.save('position');
	 *   element.style.position = 'absolute';
	 *   styles.restore(); // restores to state before the assignment above
	 *
	 * @param {Element} element
	 */
	function StyleRestorer(element) {
		var self = {};
		self.style = {};
		self.save = function () {
			if (!element || !element.nodeType) return;
			for (var i = 0; i < arguments.length; i++) {
				self.style[arguments[i]] = element.style[arguments[i]];
			}
			return self;
		};
		self.restore = function () {
			if (!element || !element.nodeType) return;
			for (var prop in self.style) {
				if (self.style.hasOwnProperty(prop)) element.style[prop] = self.style[prop];
			}
			return self;
		};
		return self;
	}

	// Local WindowAnimationTiming interface polyfill
	(function (w) {
		rAF = w.requestAnimationFrame
			|| w.webkitRequestAnimationFrame
			|| fallback;

		/**
		* Fallback implementation.
		*/
		var prev = new Date().getTime();
		function fallback(fn) {
			var curr = new Date().getTime();
			var ms = Math.max(0, 16 - (curr - prev));
			var req = setTimeout(fn, ms);
			prev = curr;
			return req;
		}

		/**
		* Cancel.
		*/
		var cancel = w.cancelAnimationFrame
			|| w.webkitCancelAnimationFrame
			|| w.clearTimeout;

		cAF = function(id){
			cancel.call(w, id);
		};
	}(window));

	// Feature detects
	(function () {
		var prefixes = ['', 'Webkit', 'Moz', 'ms', 'O'];
		var el = document.createElement('div');

		function testProp(prop) {
			for (var p = 0, pl = prefixes.length; p < pl; p++) {
				var prefixedProp = prefixes[p] ? prefixes[p] + prop.charAt(0).toUpperCase() + prop.slice(1) : prop;
				if (el.style[prefixedProp] != null) {
					return prefixedProp;
				}
			}
		}

		// Global support indicators
		transform = testProp('transform');
		gpuAcceleration = testProp('perspective') ? 'translateZ(0) ' : '';
	}());

	// Expose class globally
	w[className] = Sly;

	// jQuery proxy
	$.fn[pluginName] = function (options, callbackMap) {
		var method, methodArgs;

		// Attributes logic
		if (!$.isPlainObject(options)) {
			if (type(options) === 'string' || options === false) {
				method = options === false ? 'destroy' : options;
				methodArgs = Array.prototype.slice.call(arguments, 1);
			}
			options = {};
		}

		// Apply to all elements
		return this.each(function (i, element) {
			// Call with prevention against multiple instantiations
			var plugin = $.data(element, namespace);

			if (!plugin && !method) {
				// Create a new object if it doesn't exist yet
				plugin = $.data(element, namespace, new Sly(element, options, callbackMap).init());
			} else if (plugin && method) {
				// Call method
				if (plugin[method]) {
					plugin[method].apply(plugin, methodArgs);
				}
			}
		});
	};

	// Default options
	Sly.defaults = {
		slidee:     null,  // Selector, DOM element, or jQuery object with DOM element representing SLIDEE.
		horizontal: false, // Switch to horizontal mode.

		// Item based navigation
		itemNav:        null,  // Item navigation type. Can be: 'basic', 'centered', 'forceCentered'.
		itemSelector:   null,  // Select only items that match this selector.
		smart:          false, // Repositions the activated item to help with further navigation.
		activateOn:     null,  // Activate an item on this event. Can be: 'click', 'mouseenter', ...
		activateMiddle: false, // Always activate the item in the middle of the FRAME. forceCentered only.

		// Scrolling
		scrollSource: null,  // Element for catching the mouse wheel scrolling. Default is FRAME.
		scrollBy:     0,     // Pixels or items to move per one mouse scroll. 0 to disable scrolling.
		scrollHijack: 300,   // Milliseconds since last wheel event after which it is acceptable to hijack global scroll.
		scrollTrap:   false, // Don't bubble scrolling when hitting scrolling limits.

		// Dragging
		dragSource:    null,  // Selector or DOM element for catching dragging events. Default is FRAME.
		mouseDragging: false, // Enable navigation by dragging the SLIDEE with mouse cursor.
		touchDragging: false, // Enable navigation by dragging the SLIDEE with touch events.
		releaseSwing:  false, // Ease out on dragging swing release.
		swingSpeed:    0.2,   // Swing synchronization speed, where: 1 = instant, 0 = infinite.
		elasticBounds: false, // Stretch SLIDEE position limits when dragging past FRAME boundaries.
		dragThreshold: 3,     // Distance in pixels before Sly recognizes dragging.
		interactive:   null,  // Selector for special interactive elements.

		// Scrollbar
		scrollBar:     null,  // Selector or DOM element for scrollbar container.
		dragHandle:    false, // Whether the scrollbar handle should be draggable.
		dynamicHandle: false, // Scrollbar handle represents the ratio between hidden and visible content.
		minHandleSize: 50,    // Minimal height or width (depends on sly direction) of a handle in pixels.
		clickBar:      false, // Enable navigation by clicking on scrollbar.
		syncSpeed:     0.5,   // Handle => SLIDEE synchronization speed, where: 1 = instant, 0 = infinite.

		// Pagesbar
		pagesBar:       null, // Selector or DOM element for pages bar container.
		activatePageOn: null, // Event used to activate page. Can be: click, mouseenter, ...
		pageBuilder:          // Page item generator.
			function (index) {
				return '<li>' + (index + 1) + '</li>';
			},

		// Navigation buttons
		forward:  null, // Selector or DOM element for "forward movement" button.
		backward: null, // Selector or DOM element for "backward movement" button.
		prev:     null, // Selector or DOM element for "previous item" button.
		next:     null, // Selector or DOM element for "next item" button.
		prevPage: null, // Selector or DOM element for "previous page" button.
		nextPage: null, // Selector or DOM element for "next page" button.

		// Automated cycling
		cycleBy:       null,  // Enable automatic cycling by 'items' or 'pages'.
		cycleInterval: 5000,  // Delay between cycles in milliseconds.
		pauseOnHover:  false, // Pause cycling when mouse hovers over the FRAME.
		startPaused:   false, // Whether to start in paused sate.

		// Mixed options
		moveBy:        300,     // Speed in pixels per second used by forward and backward buttons.
		speed:         0,       // Animations speed in milliseconds. 0 to disable animations.
		easing:        'swing', // Easing for duration based (tweening) animations.
		startAt:       null,    // Starting offset in pixels or items.
		keyboardNavBy: null,    // Enable keyboard navigation by 'items' or 'pages'.

		// Classes
		draggedClass:  'dragged', // Class for dragged elements (like SLIDEE or scrollbar handle).
		activeClass:   'active',  // Class for active items and pages.
		disabledClass: 'disabled' // Class for disabled navigation elements.
	};
}(jQuery, window));
;
(function ($) {
    var m = {
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        s = {
            'array': function (x) {
                var a = ['['], b, f, i, l = x.length, v;
                for (i = 0; i < l; i += 1) {
                    v = x[i];
                    f = s[typeof v];
                    if (f) {
                        v = f(v);
                        if (typeof v == 'string') {
                            if (b) {
                                a[a.length] = ',';
                            }
                            a[a.length] = v;
                            b = true;
                        }
                    }
                }
                a[a.length] = ']';
                return a.join('');
            },
            'boolean': function (x) {
                return String(x);
            },
            'null': function (x) {
                return "null";
            },
            'number': function (x) {
                return isFinite(x) ? String(x) : 'null';
            },
            'object': function (x) {
                if (x) {
                    if (x instanceof Array) {
                        return s.array(x);
                    }
                    var a = ['{'], b, f, i, v;
                    for (i in x) {
                        v = x[i];
                        f = s[typeof v];
                        if (f) {
                            v = f(v);
                            if (typeof v == 'string') {
                                if (b) {
                                    a[a.length] = ',';
                                }
                                a.push(s.string(i), ':', v);
                                b = true;
                            }
                        }
                    }
                    a[a.length] = '}';
                    return a.join('');
                }
                return 'null';
            },
            'string': function (x) {
                if (/["\\\x00-\x1f]/.test(x)) {
                    x = x.replace(/([\x00-\x1f\\"])/g, function(a, b) {
                        var c = m[b];
                        if (c) {
                            return c;
                        }
                        c = b.charCodeAt();
                        return '\\u00' +
                            Math.floor(c / 16).toString(16) +
                            (c % 16).toString(16);
                    });
                }
                return '"' + x + '"';
            }
        };

	$.toJSON = function(v) {
		var f = isNaN(v) ? s[typeof v] : s['number'];
		if (f) return f(v);
	};
	
	$.parseJSON = function(v, safe) {
		if (safe === undefined) safe = $.parseJSON.safe;
		if (safe && !/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.test(v))
			return undefined;
		return eval('('+v+')');
	};
	
	$.parseJSON.safe = false;

})(jQuery);
;
(function () { function r(e, n, t) { function o(i, f) { if (!n[i]) { if (!e[i]) { var c = "function" == typeof require && require; if (!f && c) return c(i, !0); if (u) return u(i, !0); var a = new Error("Cannot find module '" + i + "'"); throw a.code = "MODULE_NOT_FOUND", a } var p = n[i] = { exports: {} }; e[i][0].call(p.exports, function (r) { var n = e[i][1][r]; return o(n || r) }, p, p.exports, r, e, n, t) } return n[i].exports } for (var u = "function" == typeof require && require, i = 0; i < t.length; i++)o(t[i]); return o } return r })()({
    1: [function (require, module, exports) {
        /**
         * default settings
         *
         * @author Zongmin Lei<leizongmin@gmail.com>
         */

        var FilterCSS = require("cssfilter").FilterCSS;
        var getDefaultCSSWhiteList = require("cssfilter").getDefaultWhiteList;
        var _ = require("./util");

        function getDefaultWhiteList() {
            return {
                a: ["target", "href", "title"],
                abbr: ["title"],
                address: [],
                area: ["shape", "coords", "href", "alt"],
                article: [],
                aside: [],
                audio: [
                    "autoplay",
                    "controls",
                    "crossorigin",
                    "loop",
                    "muted",
                    "preload",
                    "src",
                ],
                b: [],
                bdi: ["dir"],
                bdo: ["dir"],
                big: [],
                blockquote: ["cite"],
                br: [],
                caption: [],
                center: [],
                cite: [],
                code: [],
                col: ["align", "valign", "span", "width"],
                colgroup: ["align", "valign", "span", "width"],
                dd: [],
                del: ["datetime"],
                details: ["open"],
                div: [],
                dl: [],
                dt: [],
                em: [],
                figcaption: [],
                figure: [],
                font: ["color", "size", "face"],
                footer: [],
                h1: [],
                h2: [],
                h3: [],
                h4: [],
                h5: [],
                h6: [],
                header: [],
                hr: [],
                i: [],
                img: ["src", "alt", "title", "width", "height"],
                ins: ["datetime"],
                li: [],
                mark: [],
                nav: [],
                ol: [],
                p: [],
                pre: [],
                s: [],
                section: [],
                small: [],
                span: [],
                sub: [],
                summary: [],
                sup: [],
                strong: [],
                strike: [],
                table: ["width", "border", "align", "valign"],
                tbody: ["align", "valign"],
                td: ["width", "rowspan", "colspan", "align", "valign"],
                tfoot: ["align", "valign"],
                th: ["width", "rowspan", "colspan", "align", "valign"],
                thead: ["align", "valign"],
                tr: ["rowspan", "align", "valign"],
                tt: [],
                u: [],
                ul: [],
                video: [
                    "autoplay",
                    "controls",
                    "crossorigin",
                    "loop",
                    "muted",
                    "playsinline",
                    "poster",
                    "preload",
                    "src",
                    "height",
                    "width",
                ],
            };
        }

        var defaultCSSFilter = new FilterCSS();

        /**
         * default onTag function
         *
         * @param {String} tag
         * @param {String} html
         * @param {Object} options
         * @return {String}
         */
        function onTag(tag, html, options) {
            // do nothing
        }

        /**
         * default onIgnoreTag function
         *
         * @param {String} tag
         * @param {String} html
         * @param {Object} options
         * @return {String}
         */
        function onIgnoreTag(tag, html, options) {
            // do nothing
        }

        /**
         * default onTagAttr function
         *
         * @param {String} tag
         * @param {String} name
         * @param {String} value
         * @return {String}
         */
        function onTagAttr(tag, name, value) {
            // do nothing
        }

        /**
         * default onIgnoreTagAttr function
         *
         * @param {String} tag
         * @param {String} name
         * @param {String} value
         * @return {String}
         */
        function onIgnoreTagAttr(tag, name, value) {
            // do nothing
        }

        /**
         * default escapeHtml function
         *
         * @param {String} html
         */
        function escapeHtml(html) {
            return html.replace(REGEXP_LT, "&lt;").replace(REGEXP_GT, "&gt;");
        }

        /**
         * default safeAttrValue function
         *
         * @param {String} tag
         * @param {String} name
         * @param {String} value
         * @param {Object} cssFilter
         * @return {String}
         */
        function safeAttrValue(tag, name, value, cssFilter) {
            // unescape attribute value firstly
            value = friendlyAttrValue(value);

            if (name === "href" || name === "src") {
                // filter `href` and `src` attribute
                // only allow the value that starts with `http://` | `https://` | `mailto:` | `/` | `#`
                value = _.trim(value);
                if (value === "#") return "#";
                if (
                    !(
                        value.substr(0, 7) === "http://" ||
                        value.substr(0, 8) === "https://" ||
                        value.substr(0, 7) === "mailto:" ||
                        value.substr(0, 4) === "tel:" ||
                        value.substr(0, 11) === "data:image/" ||
                        value.substr(0, 6) === "ftp://" ||
                        value.substr(0, 2) === "./" ||
                        value.substr(0, 3) === "../" ||
                        value[0] === "#" ||
                        value[0] === "/"
                    )
                ) {
                    return "";
                }
            } else if (name === "background") {
                // filter `background` attribute (maybe no use)
                // `javascript:`
                REGEXP_DEFAULT_ON_TAG_ATTR_4.lastIndex = 0;
                if (REGEXP_DEFAULT_ON_TAG_ATTR_4.test(value)) {
                    return "";
                }
            } else if (name === "style") {
                // `expression()`
                REGEXP_DEFAULT_ON_TAG_ATTR_7.lastIndex = 0;
                if (REGEXP_DEFAULT_ON_TAG_ATTR_7.test(value)) {
                    return "";
                }
                // `url()`
                REGEXP_DEFAULT_ON_TAG_ATTR_8.lastIndex = 0;
                if (REGEXP_DEFAULT_ON_TAG_ATTR_8.test(value)) {
                    REGEXP_DEFAULT_ON_TAG_ATTR_4.lastIndex = 0;
                    if (REGEXP_DEFAULT_ON_TAG_ATTR_4.test(value)) {
                        return "";
                    }
                }
                if (cssFilter !== false) {
                    cssFilter = cssFilter || defaultCSSFilter;
                    value = cssFilter.process(value);
                }
            }

            // escape `<>"` before returns
            value = escapeAttrValue(value);
            return value;
        }

        // RegExp list
        var REGEXP_LT = /</g;
        var REGEXP_GT = />/g;
        var REGEXP_QUOTE = /"/g;
        var REGEXP_QUOTE_2 = /&quot;/g;
        var REGEXP_ATTR_VALUE_1 = /&#([a-zA-Z0-9]*);?/gim;
        var REGEXP_ATTR_VALUE_COLON = /&colon;?/gim;
        var REGEXP_ATTR_VALUE_NEWLINE = /&newline;?/gim;
        var REGEXP_DEFAULT_ON_TAG_ATTR_3 = /\/\*|\*\//gm;
        var REGEXP_DEFAULT_ON_TAG_ATTR_4 = /((j\s*a\s*v\s*a|v\s*b|l\s*i\s*v\s*e)\s*s\s*c\s*r\s*i\s*p\s*t\s*|m\s*o\s*c\s*h\s*a)\:/gi;
        var REGEXP_DEFAULT_ON_TAG_ATTR_5 = /^[\s"'`]*(d\s*a\s*t\s*a\s*)\:/gi;
        var REGEXP_DEFAULT_ON_TAG_ATTR_6 = /^[\s"'`]*(d\s*a\s*t\s*a\s*)\:\s*image\//gi;
        var REGEXP_DEFAULT_ON_TAG_ATTR_7 = /e\s*x\s*p\s*r\s*e\s*s\s*s\s*i\s*o\s*n\s*\(.*/gi;
        var REGEXP_DEFAULT_ON_TAG_ATTR_8 = /u\s*r\s*l\s*\(.*/gi;

        /**
         * escape double quote
         *
         * @param {String} str
         * @return {String} str
         */
        function escapeQuote(str) {
            return str.replace(REGEXP_QUOTE, "&quot;");
        }

        /**
         * unescape double quote
         *
         * @param {String} str
         * @return {String} str
         */
        function unescapeQuote(str) {
            return str.replace(REGEXP_QUOTE_2, '"');
        }

        /**
         * escape html entities
         *
         * @param {String} str
         * @return {String}
         */
        function escapeHtmlEntities(str) {
            return str.replace(REGEXP_ATTR_VALUE_1, function replaceUnicode(str, code) {
                return code[0] === "x" || code[0] === "X"
                    ? String.fromCharCode(parseInt(code.substr(1), 16))
                    : String.fromCharCode(parseInt(code, 10));
            });
        }

        /**
         * escape html5 new danger entities
         *
         * @param {String} str
         * @return {String}
         */
        function escapeDangerHtml5Entities(str) {
            return str
                .replace(REGEXP_ATTR_VALUE_COLON, ":")
                .replace(REGEXP_ATTR_VALUE_NEWLINE, " ");
        }

        /**
         * clear nonprintable characters
         *
         * @param {String} str
         * @return {String}
         */
        function clearNonPrintableCharacter(str) {
            var str2 = "";
            for (var i = 0, len = str.length; i < len; i++) {
                str2 += str.charCodeAt(i) < 32 ? " " : str.charAt(i);
            }
            return _.trim(str2);
        }

        /**
         * get friendly attribute value
         *
         * @param {String} str
         * @return {String}
         */
        function friendlyAttrValue(str) {
            str = unescapeQuote(str);
            str = escapeHtmlEntities(str);
            str = escapeDangerHtml5Entities(str);
            str = clearNonPrintableCharacter(str);
            return str;
        }

        /**
         * unescape attribute value
         *
         * @param {String} str
         * @return {String}
         */
        function escapeAttrValue(str) {
            str = escapeQuote(str);
            str = escapeHtml(str);
            return str;
        }

        /**
         * `onIgnoreTag` function for removing all the tags that are not in whitelist
         */
        function onIgnoreTagStripAll() {
            return "";
        }

        /**
         * remove tag body
         * specify a `tags` list, if the tag is not in the `tags` list then process by the specify function (optional)
         *
         * @param {array} tags
         * @param {function} next
         */
        function StripTagBody(tags, next) {
            if (typeof next !== "function") {
                next = function () { };
            }

            var isRemoveAllTag = !Array.isArray(tags);
            function isRemoveTag(tag) {
                if (isRemoveAllTag) return true;
                return _.indexOf(tags, tag) !== -1;
            }

            var removeList = [];
            var posStart = false;

            return {
                onIgnoreTag: function (tag, html, options) {
                    if (isRemoveTag(tag)) {
                        if (options.isClosing) {
                            var ret = "[/removed]";
                            var end = options.position + ret.length;
                            removeList.push([
                                posStart !== false ? posStart : options.position,
                                end,
                            ]);
                            posStart = false;
                            return ret;
                        } else {
                            if (!posStart) {
                                posStart = options.position;
                            }
                            return "[removed]";
                        }
                    } else {
                        return next(tag, html, options);
                    }
                },
                remove: function (html) {
                    var rethtml = "";
                    var lastPos = 0;
                    _.forEach(removeList, function (pos) {
                        rethtml += html.slice(lastPos, pos[0]);
                        lastPos = pos[1];
                    });
                    rethtml += html.slice(lastPos);
                    return rethtml;
                },
            };
        }

        /**
         * remove html comments
         *
         * @param {String} html
         * @return {String}
         */
        function stripCommentTag(html) {
            return html.replace(STRIP_COMMENT_TAG_REGEXP, "");
        }
        var STRIP_COMMENT_TAG_REGEXP = /<!--[\s\S]*?-->/g;

        /**
         * remove invisible characters
         *
         * @param {String} html
         * @return {String}
         */
        function stripBlankChar(html) {
            var chars = html.split("");
            chars = chars.filter(function (char) {
                var c = char.charCodeAt(0);
                if (c === 127) return false;
                if (c <= 31) {
                    if (c === 10 || c === 13) return true;
                    return false;
                }
                return true;
            });
            return chars.join("");
        }

        exports.whiteList = getDefaultWhiteList();
        exports.getDefaultWhiteList = getDefaultWhiteList;
        exports.onTag = onTag;
        exports.onIgnoreTag = onIgnoreTag;
        exports.onTagAttr = onTagAttr;
        exports.onIgnoreTagAttr = onIgnoreTagAttr;
        exports.safeAttrValue = safeAttrValue;
        exports.escapeHtml = escapeHtml;
        exports.escapeQuote = escapeQuote;
        exports.unescapeQuote = unescapeQuote;
        exports.escapeHtmlEntities = escapeHtmlEntities;
        exports.escapeDangerHtml5Entities = escapeDangerHtml5Entities;
        exports.clearNonPrintableCharacter = clearNonPrintableCharacter;
        exports.friendlyAttrValue = friendlyAttrValue;
        exports.escapeAttrValue = escapeAttrValue;
        exports.onIgnoreTagStripAll = onIgnoreTagStripAll;
        exports.StripTagBody = StripTagBody;
        exports.stripCommentTag = stripCommentTag;
        exports.stripBlankChar = stripBlankChar;
        exports.cssFilter = defaultCSSFilter;
        exports.getDefaultCSSWhiteList = getDefaultCSSWhiteList;

    }, { "./util": 4, "cssfilter": 8 }], 2: [function (require, module, exports) {
        /**
         * xss
         *
         * @author Zongmin Lei<leizongmin@gmail.com>
         */

        var DEFAULT = require("./default");
        var parser = require("./parser");
        var FilterXSS = require("./xss");

        /**
         * filter xss function
         *
         * @param {String} html
         * @param {Object} options { whiteList, onTag, onTagAttr, onIgnoreTag, onIgnoreTagAttr, safeAttrValue, escapeHtml }
         * @return {String}
         */
        function filterXSS(html, options) {
            var xss = new FilterXSS(options);
            return xss.process(html);
        }

        exports = module.exports = filterXSS;
        exports.filterXSS = filterXSS;
        exports.FilterXSS = FilterXSS;
        for (var i in DEFAULT) exports[i] = DEFAULT[i];
        for (var i in parser) exports[i] = parser[i];

        // using `xss` on the browser, output `filterXSS` to the globals
        if (typeof window !== "undefined") {
            window.filterXSS = module.exports;
        }

        // using `xss` on the WebWorker, output `filterXSS` to the globals
        function isWorkerEnv() {
            return (
                typeof self !== "undefined" &&
                typeof DedicatedWorkerGlobalScope !== "undefined" &&
                self instanceof DedicatedWorkerGlobalScope
            );
        }
        if (isWorkerEnv()) {
            self.filterXSS = module.exports;
        }

    }, { "./default": 1, "./parser": 3, "./xss": 5 }], 3: [function (require, module, exports) {
        /**
         * Simple HTML Parser
         *
         * @author Zongmin Lei<leizongmin@gmail.com>
         */

        var _ = require("./util");

        /**
         * get tag name
         *
         * @param {String} html e.g. '<a hef="#">'
         * @return {String}
         */
        function getTagName(html) {
            var i = _.spaceIndex(html);
            if (i === -1) {
                var tagName = html.slice(1, -1);
            } else {
                var tagName = html.slice(1, i + 1);
            }
            tagName = _.trim(tagName).toLowerCase();
            if (tagName.slice(0, 1) === "/") tagName = tagName.slice(1);
            if (tagName.slice(-1) === "/") tagName = tagName.slice(0, -1);
            return tagName;
        }

        /**
         * is close tag?
         *
         * @param {String} html 如：'<a hef="#">'
         * @return {Boolean}
         */
        function isClosing(html) {
            return html.slice(0, 2) === "</";
        }

        /**
         * parse input html and returns processed html
         *
         * @param {String} html
         * @param {Function} onTag e.g. function (sourcePosition, position, tag, html, isClosing)
         * @param {Function} escapeHtml
         * @return {String}
         */
        function parseTag(html, onTag, escapeHtml) {
            "use strict";

            var rethtml = "";
            var lastPos = 0;
            var tagStart = false;
            var quoteStart = false;
            var currentPos = 0;
            var len = html.length;
            var currentTagName = "";
            var currentHtml = "";

            chariterator: for (currentPos = 0; currentPos < len; currentPos++) {
                var c = html.charAt(currentPos);
                if (tagStart === false) {
                    if (c === "<") {
                        tagStart = currentPos;
                        continue;
                    }
                } else {
                    if (quoteStart === false) {
                        if (c === "<") {
                            rethtml += escapeHtml(html.slice(lastPos, currentPos));
                            tagStart = currentPos;
                            lastPos = currentPos;
                            continue;
                        }
                        if (c === ">") {
                            rethtml += escapeHtml(html.slice(lastPos, tagStart));
                            currentHtml = html.slice(tagStart, currentPos + 1);
                            currentTagName = getTagName(currentHtml);
                            rethtml += onTag(
                                tagStart,
                                rethtml.length,
                                currentTagName,
                                currentHtml,
                                isClosing(currentHtml)
                            );
                            lastPos = currentPos + 1;
                            tagStart = false;
                            continue;
                        }
                        if (c === '"' || c === "'") {
                            var i = 1;
                            var ic = html.charAt(currentPos - i);

                            while (ic.trim() === "" || ic === "=") {
                                if (ic === "=") {
                                    quoteStart = c;
                                    continue chariterator;
                                }
                                ic = html.charAt(currentPos - ++i);
                            }
                        }
                    } else {
                        if (c === quoteStart) {
                            quoteStart = false;
                            continue;
                        }
                    }
                }
            }
            if (lastPos < html.length) {
                rethtml += escapeHtml(html.substr(lastPos));
            }

            return rethtml;
        }

        var REGEXP_ILLEGAL_ATTR_NAME = /[^a-zA-Z0-9_:\.\-]/gim;

        /**
         * parse input attributes and returns processed attributes
         *
         * @param {String} html e.g. `href="#" target="_blank"`
         * @param {Function} onAttr e.g. `function (name, value)`
         * @return {String}
         */
        function parseAttr(html, onAttr) {
            "use strict";

            var lastPos = 0;
            var retAttrs = [];
            var tmpName = false;
            var len = html.length;

            function addAttr(name, value) {
                name = _.trim(name);
                name = name.replace(REGEXP_ILLEGAL_ATTR_NAME, "").toLowerCase();
                if (name.length < 1) return;
                var ret = onAttr(name, value || "");
                if (ret) retAttrs.push(ret);
            }

            // 逐个分析字符
            for (var i = 0; i < len; i++) {
                var c = html.charAt(i);
                var v, j;
                if (tmpName === false && c === "=") {
                    tmpName = html.slice(lastPos, i);
                    lastPos = i + 1;
                    continue;
                }
                if (tmpName !== false) {
                    if (
                        i === lastPos &&
                        (c === '"' || c === "'") &&
                        html.charAt(i - 1) === "="
                    ) {
                        j = html.indexOf(c, i + 1);
                        if (j === -1) {
                            break;
                        } else {
                            v = _.trim(html.slice(lastPos + 1, j));
                            addAttr(tmpName, v);
                            tmpName = false;
                            i = j;
                            lastPos = i + 1;
                            continue;
                        }
                    }
                }
                if (/\s|\n|\t/.test(c)) {
                    html = html.replace(/\s|\n|\t/g, " ");
                    if (tmpName === false) {
                        j = findNextEqual(html, i);
                        if (j === -1) {
                            v = _.trim(html.slice(lastPos, i));
                            addAttr(v);
                            tmpName = false;
                            lastPos = i + 1;
                            continue;
                        } else {
                            i = j - 1;
                            continue;
                        }
                    } else {
                        j = findBeforeEqual(html, i - 1);
                        if (j === -1) {
                            v = _.trim(html.slice(lastPos, i));
                            v = stripQuoteWrap(v);
                            addAttr(tmpName, v);
                            tmpName = false;
                            lastPos = i + 1;
                            continue;
                        } else {
                            continue;
                        }
                    }
                }
            }

            if (lastPos < html.length) {
                if (tmpName === false) {
                    addAttr(html.slice(lastPos));
                } else {
                    addAttr(tmpName, stripQuoteWrap(_.trim(html.slice(lastPos))));
                }
            }

            return _.trim(retAttrs.join(" "));
        }

        function findNextEqual(str, i) {
            for (; i < str.length; i++) {
                var c = str[i];
                if (c === " ") continue;
                if (c === "=") return i;
                return -1;
            }
        }

        function findBeforeEqual(str, i) {
            for (; i > 0; i--) {
                var c = str[i];
                if (c === " ") continue;
                if (c === "=") return i;
                return -1;
            }
        }

        function isQuoteWrapString(text) {
            if (
                (text[0] === '"' && text[text.length - 1] === '"') ||
                (text[0] === "'" && text[text.length - 1] === "'")
            ) {
                return true;
            } else {
                return false;
            }
        }

        function stripQuoteWrap(text) {
            if (isQuoteWrapString(text)) {
                return text.substr(1, text.length - 2);
            } else {
                return text;
            }
        }

        exports.parseTag = parseTag;
        exports.parseAttr = parseAttr;

    }, { "./util": 4 }], 4: [function (require, module, exports) {
        module.exports = {
            indexOf: function (arr, item) {
                var i, j;
                if (Array.prototype.indexOf) {
                    return arr.indexOf(item);
                }
                for (i = 0, j = arr.length; i < j; i++) {
                    if (arr[i] === item) {
                        return i;
                    }
                }
                return -1;
            },
            forEach: function (arr, fn, scope) {
                var i, j;
                if (Array.prototype.forEach) {
                    return arr.forEach(fn, scope);
                }
                for (i = 0, j = arr.length; i < j; i++) {
                    fn.call(scope, arr[i], i, arr);
                }
            },
            trim: function (str) {
                if (String.prototype.trim) {
                    return str.trim();
                }
                return str.replace(/(^\s*)|(\s*$)/g, "");
            },
            spaceIndex: function (str) {
                var reg = /\s|\n|\t/;
                var match = reg.exec(str);
                return match ? match.index : -1;
            },
        };

    }, {}], 5: [function (require, module, exports) {
        /**
         * filter xss
         *
         * @author Zongmin Lei<leizongmin@gmail.com>
         */

        var FilterCSS = require("cssfilter").FilterCSS;
        var DEFAULT = require("./default");
        var parser = require("./parser");
        var parseTag = parser.parseTag;
        var parseAttr = parser.parseAttr;
        var _ = require("./util");

        /**
         * returns `true` if the input value is `undefined` or `null`
         *
         * @param {Object} obj
         * @return {Boolean}
         */
        function isNull(obj) {
            return obj === undefined || obj === null;
        }

        /**
         * get attributes for a tag
         *
         * @param {String} html
         * @return {Object}
         *   - {String} html
         *   - {Boolean} closing
         */
        function getAttrs(html) {
            var i = _.spaceIndex(html);
            if (i === -1) {
                return {
                    html: "",
                    closing: html[html.length - 2] === "/",
                };
            }
            html = _.trim(html.slice(i + 1, -1));
            var isClosing = html[html.length - 1] === "/";
            if (isClosing) html = _.trim(html.slice(0, -1));
            return {
                html: html,
                closing: isClosing,
            };
        }

        /**
         * shallow copy
         *
         * @param {Object} obj
         * @return {Object}
         */
        function shallowCopyObject(obj) {
            var ret = {};
            for (var i in obj) {
                ret[i] = obj[i];
            }
            return ret;
        }

        /**
         * FilterXSS class
         *
         * @param {Object} options
         *        whiteList, onTag, onTagAttr, onIgnoreTag,
         *        onIgnoreTagAttr, safeAttrValue, escapeHtml
         *        stripIgnoreTagBody, allowCommentTag, stripBlankChar
         *        css{whiteList, onAttr, onIgnoreAttr} `css=false` means don't use `cssfilter`
         */
        function FilterXSS(options) {
            options = shallowCopyObject(options || {});

            if (options.stripIgnoreTag) {
                if (options.onIgnoreTag) {
                    console.error(
                        'Notes: cannot use these two options "stripIgnoreTag" and "onIgnoreTag" at the same time'
                    );
                }
                options.onIgnoreTag = DEFAULT.onIgnoreTagStripAll;
            }

            options.whiteList = options.whiteList || DEFAULT.whiteList;
            options.onTag = options.onTag || DEFAULT.onTag;
            options.onTagAttr = options.onTagAttr || DEFAULT.onTagAttr;
            options.onIgnoreTag = options.onIgnoreTag || DEFAULT.onIgnoreTag;
            options.onIgnoreTagAttr = options.onIgnoreTagAttr || DEFAULT.onIgnoreTagAttr;
            options.safeAttrValue = options.safeAttrValue || DEFAULT.safeAttrValue;
            options.escapeHtml = options.escapeHtml || DEFAULT.escapeHtml;
            this.options = options;

            if (options.css === false) {
                this.cssFilter = false;
            } else {
                options.css = options.css || {};
                this.cssFilter = new FilterCSS(options.css);
            }
        }

        /**
         * start process and returns result
         *
         * @param {String} html
         * @return {String}
         */
        FilterXSS.prototype.process = function (html) {
            // compatible with the input
            html = html || "";
            html = html.toString();
            if (!html) return "";

            var me = this;
            var options = me.options;
            var whiteList = options.whiteList;
            var onTag = options.onTag;
            var onIgnoreTag = options.onIgnoreTag;
            var onTagAttr = options.onTagAttr;
            var onIgnoreTagAttr = options.onIgnoreTagAttr;
            var safeAttrValue = options.safeAttrValue;
            var escapeHtml = options.escapeHtml;
            var cssFilter = me.cssFilter;

            // remove invisible characters
            if (options.stripBlankChar) {
                html = DEFAULT.stripBlankChar(html);
            }

            // remove html comments
            if (!options.allowCommentTag) {
                html = DEFAULT.stripCommentTag(html);
            }

            // if enable stripIgnoreTagBody
            var stripIgnoreTagBody = false;
            if (options.stripIgnoreTagBody) {
                var stripIgnoreTagBody = DEFAULT.StripTagBody(
                    options.stripIgnoreTagBody,
                    onIgnoreTag
                );
                onIgnoreTag = stripIgnoreTagBody.onIgnoreTag;
            }

            var retHtml = parseTag(
                html,
                function (sourcePosition, position, tag, html, isClosing) {
                    var info = {
                        sourcePosition: sourcePosition,
                        position: position,
                        isClosing: isClosing,
                        isWhite: whiteList.hasOwnProperty(tag),
                    };

                    // call `onTag()`
                    var ret = onTag(tag, html, info);
                    if (!isNull(ret)) return ret;

                    if (info.isWhite) {
                        if (info.isClosing) {
                            return "</" + tag + ">";
                        }

                        var attrs = getAttrs(html);
                        var whiteAttrList = whiteList[tag];
                        var attrsHtml = parseAttr(attrs.html, function (name, value) {
                            // call `onTagAttr()`
                            var isWhiteAttr = _.indexOf(whiteAttrList, name) !== -1;
                            var ret = onTagAttr(tag, name, value, isWhiteAttr);
                            if (!isNull(ret)) return ret;

                            if (isWhiteAttr) {
                                // call `safeAttrValue()`
                                value = safeAttrValue(tag, name, value, cssFilter);
                                if (value) {
                                    return name + '="' + value + '"';
                                } else {
                                    return name;
                                }
                            } else {
                                // call `onIgnoreTagAttr()`
                                var ret = onIgnoreTagAttr(tag, name, value, isWhiteAttr);
                                if (!isNull(ret)) return ret;
                                return;
                            }
                        });

                        // build new tag html
                        var html = "<" + tag;
                        if (attrsHtml) html += " " + attrsHtml;
                        if (attrs.closing) html += " /";
                        html += ">";
                        return html;
                    } else {
                        // call `onIgnoreTag()`
                        var ret = onIgnoreTag(tag, html, info);
                        if (!isNull(ret)) return ret;
                        return escapeHtml(html);
                    }
                },
                escapeHtml
            );

            // if enable stripIgnoreTagBody
            if (stripIgnoreTagBody) {
                retHtml = stripIgnoreTagBody.remove(retHtml);
            }

            return retHtml;
        };

        module.exports = FilterXSS;

    }, { "./default": 1, "./parser": 3, "./util": 4, "cssfilter": 8 }], 6: [function (require, module, exports) {
        /**
         * cssfilter
         *
         * @author 老雷<leizongmin@gmail.com>
         */

        var DEFAULT = require('./default');
        var parseStyle = require('./parser');
        var _ = require('./util');


        /**
         * 返回值是否为空
         *
         * @param {Object} obj
         * @return {Boolean}
         */
        function isNull(obj) {
            return (obj === undefined || obj === null);
        }

        /**
         * 浅拷贝对象
         *
         * @param {Object} obj
         * @return {Object}
         */
        function shallowCopyObject(obj) {
            var ret = {};
            for (var i in obj) {
                ret[i] = obj[i];
            }
            return ret;
        }

        /**
         * 创建CSS过滤器
         *
         * @param {Object} options
         *   - {Object} whiteList
         *   - {Function} onAttr
         *   - {Function} onIgnoreAttr
         *   - {Function} safeAttrValue
         */
        function FilterCSS(options) {
            options = shallowCopyObject(options || {});
            options.whiteList = options.whiteList || DEFAULT.whiteList;
            options.onAttr = options.onAttr || DEFAULT.onAttr;
            options.onIgnoreAttr = options.onIgnoreAttr || DEFAULT.onIgnoreAttr;
            options.safeAttrValue = options.safeAttrValue || DEFAULT.safeAttrValue;
            this.options = options;
        }

        FilterCSS.prototype.process = function (css) {
            // 兼容各种奇葩输入
            css = css || '';
            css = css.toString();
            if (!css) return '';

            var me = this;
            var options = me.options;
            var whiteList = options.whiteList;
            var onAttr = options.onAttr;
            var onIgnoreAttr = options.onIgnoreAttr;
            var safeAttrValue = options.safeAttrValue;

            var retCSS = parseStyle(css, function (sourcePosition, position, name, value, source) {

                var check = whiteList[name];
                var isWhite = false;
                if (check === true) isWhite = check;
                else if (typeof check === 'function') isWhite = check(value);
                else if (check instanceof RegExp) isWhite = check.test(value);
                if (isWhite !== true) isWhite = false;

                // 如果过滤后 value 为空则直接忽略
                value = safeAttrValue(name, value);
                if (!value) return;

                var opts = {
                    position: position,
                    sourcePosition: sourcePosition,
                    source: source,
                    isWhite: isWhite
                };

                if (isWhite) {

                    var ret = onAttr(name, value, opts);
                    if (isNull(ret)) {
                        return name + ':' + value;
                    } else {
                        return ret;
                    }

                } else {

                    var ret = onIgnoreAttr(name, value, opts);
                    if (!isNull(ret)) {
                        return ret;
                    }

                }
            });

            return retCSS;
        };


        module.exports = FilterCSS;

    }, { "./default": 7, "./parser": 9, "./util": 10 }], 7: [function (require, module, exports) {
        /**
         * cssfilter
         *
         * @author 老雷<leizongmin@gmail.com>
         */

        function getDefaultWhiteList() {
            // 白名单值说明：
            // true: 允许该属性
            // Function: function (val) { } 返回true表示允许该属性，其他值均表示不允许
            // RegExp: regexp.test(val) 返回true表示允许该属性，其他值均表示不允许
            // 除上面列出的值外均表示不允许
            var whiteList = {};

            whiteList['align-content'] = false; // default: auto
            whiteList['align-items'] = false; // default: auto
            whiteList['align-self'] = false; // default: auto
            whiteList['alignment-adjust'] = false; // default: auto
            whiteList['alignment-baseline'] = false; // default: baseline
            whiteList['all'] = false; // default: depending on individual properties
            whiteList['anchor-point'] = false; // default: none
            whiteList['animation'] = false; // default: depending on individual properties
            whiteList['animation-delay'] = false; // default: 0
            whiteList['animation-direction'] = false; // default: normal
            whiteList['animation-duration'] = false; // default: 0
            whiteList['animation-fill-mode'] = false; // default: none
            whiteList['animation-iteration-count'] = false; // default: 1
            whiteList['animation-name'] = false; // default: none
            whiteList['animation-play-state'] = false; // default: running
            whiteList['animation-timing-function'] = false; // default: ease
            whiteList['azimuth'] = false; // default: center
            whiteList['backface-visibility'] = false; // default: visible
            whiteList['background'] = true; // default: depending on individual properties
            whiteList['background-attachment'] = true; // default: scroll
            whiteList['background-clip'] = true; // default: border-box
            whiteList['background-color'] = true; // default: transparent
            whiteList['background-image'] = true; // default: none
            whiteList['background-origin'] = true; // default: padding-box
            whiteList['background-position'] = true; // default: 0% 0%
            whiteList['background-repeat'] = true; // default: repeat
            whiteList['background-size'] = true; // default: auto
            whiteList['baseline-shift'] = false; // default: baseline
            whiteList['binding'] = false; // default: none
            whiteList['bleed'] = false; // default: 6pt
            whiteList['bookmark-label'] = false; // default: content()
            whiteList['bookmark-level'] = false; // default: none
            whiteList['bookmark-state'] = false; // default: open
            whiteList['border'] = true; // default: depending on individual properties
            whiteList['border-bottom'] = true; // default: depending on individual properties
            whiteList['border-bottom-color'] = true; // default: current color
            whiteList['border-bottom-left-radius'] = true; // default: 0
            whiteList['border-bottom-right-radius'] = true; // default: 0
            whiteList['border-bottom-style'] = true; // default: none
            whiteList['border-bottom-width'] = true; // default: medium
            whiteList['border-collapse'] = true; // default: separate
            whiteList['border-color'] = true; // default: depending on individual properties
            whiteList['border-image'] = true; // default: none
            whiteList['border-image-outset'] = true; // default: 0
            whiteList['border-image-repeat'] = true; // default: stretch
            whiteList['border-image-slice'] = true; // default: 100%
            whiteList['border-image-source'] = true; // default: none
            whiteList['border-image-width'] = true; // default: 1
            whiteList['border-left'] = true; // default: depending on individual properties
            whiteList['border-left-color'] = true; // default: current color
            whiteList['border-left-style'] = true; // default: none
            whiteList['border-left-width'] = true; // default: medium
            whiteList['border-radius'] = true; // default: 0
            whiteList['border-right'] = true; // default: depending on individual properties
            whiteList['border-right-color'] = true; // default: current color
            whiteList['border-right-style'] = true; // default: none
            whiteList['border-right-width'] = true; // default: medium
            whiteList['border-spacing'] = true; // default: 0
            whiteList['border-style'] = true; // default: depending on individual properties
            whiteList['border-top'] = true; // default: depending on individual properties
            whiteList['border-top-color'] = true; // default: current color
            whiteList['border-top-left-radius'] = true; // default: 0
            whiteList['border-top-right-radius'] = true; // default: 0
            whiteList['border-top-style'] = true; // default: none
            whiteList['border-top-width'] = true; // default: medium
            whiteList['border-width'] = true; // default: depending on individual properties
            whiteList['bottom'] = false; // default: auto
            whiteList['box-decoration-break'] = true; // default: slice
            whiteList['box-shadow'] = true; // default: none
            whiteList['box-sizing'] = true; // default: content-box
            whiteList['box-snap'] = true; // default: none
            whiteList['box-suppress'] = true; // default: show
            whiteList['break-after'] = true; // default: auto
            whiteList['break-before'] = true; // default: auto
            whiteList['break-inside'] = true; // default: auto
            whiteList['caption-side'] = false; // default: top
            whiteList['chains'] = false; // default: none
            whiteList['clear'] = true; // default: none
            whiteList['clip'] = false; // default: auto
            whiteList['clip-path'] = false; // default: none
            whiteList['clip-rule'] = false; // default: nonzero
            whiteList['color'] = true; // default: implementation dependent
            whiteList['color-interpolation-filters'] = true; // default: auto
            whiteList['column-count'] = false; // default: auto
            whiteList['column-fill'] = false; // default: balance
            whiteList['column-gap'] = false; // default: normal
            whiteList['column-rule'] = false; // default: depending on individual properties
            whiteList['column-rule-color'] = false; // default: current color
            whiteList['column-rule-style'] = false; // default: medium
            whiteList['column-rule-width'] = false; // default: medium
            whiteList['column-span'] = false; // default: none
            whiteList['column-width'] = false; // default: auto
            whiteList['columns'] = false; // default: depending on individual properties
            whiteList['contain'] = false; // default: none
            whiteList['content'] = false; // default: normal
            whiteList['counter-increment'] = false; // default: none
            whiteList['counter-reset'] = false; // default: none
            whiteList['counter-set'] = false; // default: none
            whiteList['crop'] = false; // default: auto
            whiteList['cue'] = false; // default: depending on individual properties
            whiteList['cue-after'] = false; // default: none
            whiteList['cue-before'] = false; // default: none
            whiteList['cursor'] = false; // default: auto
            whiteList['direction'] = false; // default: ltr
            whiteList['display'] = true; // default: depending on individual properties
            whiteList['display-inside'] = true; // default: auto
            whiteList['display-list'] = true; // default: none
            whiteList['display-outside'] = true; // default: inline-level
            whiteList['dominant-baseline'] = false; // default: auto
            whiteList['elevation'] = false; // default: level
            whiteList['empty-cells'] = false; // default: show
            whiteList['filter'] = false; // default: none
            whiteList['flex'] = false; // default: depending on individual properties
            whiteList['flex-basis'] = false; // default: auto
            whiteList['flex-direction'] = false; // default: row
            whiteList['flex-flow'] = false; // default: depending on individual properties
            whiteList['flex-grow'] = false; // default: 0
            whiteList['flex-shrink'] = false; // default: 1
            whiteList['flex-wrap'] = false; // default: nowrap
            whiteList['float'] = false; // default: none
            whiteList['float-offset'] = false; // default: 0 0
            whiteList['flood-color'] = false; // default: black
            whiteList['flood-opacity'] = false; // default: 1
            whiteList['flow-from'] = false; // default: none
            whiteList['flow-into'] = false; // default: none
            whiteList['font'] = true; // default: depending on individual properties
            whiteList['font-family'] = true; // default: implementation dependent
            whiteList['font-feature-settings'] = true; // default: normal
            whiteList['font-kerning'] = true; // default: auto
            whiteList['font-language-override'] = true; // default: normal
            whiteList['font-size'] = true; // default: medium
            whiteList['font-size-adjust'] = true; // default: none
            whiteList['font-stretch'] = true; // default: normal
            whiteList['font-style'] = true; // default: normal
            whiteList['font-synthesis'] = true; // default: weight style
            whiteList['font-variant'] = true; // default: normal
            whiteList['font-variant-alternates'] = true; // default: normal
            whiteList['font-variant-caps'] = true; // default: normal
            whiteList['font-variant-east-asian'] = true; // default: normal
            whiteList['font-variant-ligatures'] = true; // default: normal
            whiteList['font-variant-numeric'] = true; // default: normal
            whiteList['font-variant-position'] = true; // default: normal
            whiteList['font-weight'] = true; // default: normal
            whiteList['grid'] = false; // default: depending on individual properties
            whiteList['grid-area'] = false; // default: depending on individual properties
            whiteList['grid-auto-columns'] = false; // default: auto
            whiteList['grid-auto-flow'] = false; // default: none
            whiteList['grid-auto-rows'] = false; // default: auto
            whiteList['grid-column'] = false; // default: depending on individual properties
            whiteList['grid-column-end'] = false; // default: auto
            whiteList['grid-column-start'] = false; // default: auto
            whiteList['grid-row'] = false; // default: depending on individual properties
            whiteList['grid-row-end'] = false; // default: auto
            whiteList['grid-row-start'] = false; // default: auto
            whiteList['grid-template'] = false; // default: depending on individual properties
            whiteList['grid-template-areas'] = false; // default: none
            whiteList['grid-template-columns'] = false; // default: none
            whiteList['grid-template-rows'] = false; // default: none
            whiteList['hanging-punctuation'] = false; // default: none
            whiteList['height'] = true; // default: auto
            whiteList['hyphens'] = false; // default: manual
            whiteList['icon'] = false; // default: auto
            whiteList['image-orientation'] = false; // default: auto
            whiteList['image-resolution'] = false; // default: normal
            whiteList['ime-mode'] = false; // default: auto
            whiteList['initial-letters'] = false; // default: normal
            whiteList['inline-box-align'] = false; // default: last
            whiteList['justify-content'] = false; // default: auto
            whiteList['justify-items'] = false; // default: auto
            whiteList['justify-self'] = false; // default: auto
            whiteList['left'] = false; // default: auto
            whiteList['letter-spacing'] = true; // default: normal
            whiteList['lighting-color'] = true; // default: white
            whiteList['line-box-contain'] = false; // default: block inline replaced
            whiteList['line-break'] = false; // default: auto
            whiteList['line-grid'] = false; // default: match-parent
            whiteList['line-height'] = false; // default: normal
            whiteList['line-snap'] = false; // default: none
            whiteList['line-stacking'] = false; // default: depending on individual properties
            whiteList['line-stacking-ruby'] = false; // default: exclude-ruby
            whiteList['line-stacking-shift'] = false; // default: consider-shifts
            whiteList['line-stacking-strategy'] = false; // default: inline-line-height
            whiteList['list-style'] = true; // default: depending on individual properties
            whiteList['list-style-image'] = true; // default: none
            whiteList['list-style-position'] = true; // default: outside
            whiteList['list-style-type'] = true; // default: disc
            whiteList['margin'] = true; // default: depending on individual properties
            whiteList['margin-bottom'] = true; // default: 0
            whiteList['margin-left'] = true; // default: 0
            whiteList['margin-right'] = true; // default: 0
            whiteList['margin-top'] = true; // default: 0
            whiteList['marker-offset'] = false; // default: auto
            whiteList['marker-side'] = false; // default: list-item
            whiteList['marks'] = false; // default: none
            whiteList['mask'] = false; // default: border-box
            whiteList['mask-box'] = false; // default: see individual properties
            whiteList['mask-box-outset'] = false; // default: 0
            whiteList['mask-box-repeat'] = false; // default: stretch
            whiteList['mask-box-slice'] = false; // default: 0 fill
            whiteList['mask-box-source'] = false; // default: none
            whiteList['mask-box-width'] = false; // default: auto
            whiteList['mask-clip'] = false; // default: border-box
            whiteList['mask-image'] = false; // default: none
            whiteList['mask-origin'] = false; // default: border-box
            whiteList['mask-position'] = false; // default: center
            whiteList['mask-repeat'] = false; // default: no-repeat
            whiteList['mask-size'] = false; // default: border-box
            whiteList['mask-source-type'] = false; // default: auto
            whiteList['mask-type'] = false; // default: luminance
            whiteList['max-height'] = true; // default: none
            whiteList['max-lines'] = false; // default: none
            whiteList['max-width'] = true; // default: none
            whiteList['min-height'] = true; // default: 0
            whiteList['min-width'] = true; // default: 0
            whiteList['move-to'] = false; // default: normal
            whiteList['nav-down'] = false; // default: auto
            whiteList['nav-index'] = false; // default: auto
            whiteList['nav-left'] = false; // default: auto
            whiteList['nav-right'] = false; // default: auto
            whiteList['nav-up'] = false; // default: auto
            whiteList['object-fit'] = false; // default: fill
            whiteList['object-position'] = false; // default: 50% 50%
            whiteList['opacity'] = false; // default: 1
            whiteList['order'] = false; // default: 0
            whiteList['orphans'] = false; // default: 2
            whiteList['outline'] = false; // default: depending on individual properties
            whiteList['outline-color'] = false; // default: invert
            whiteList['outline-offset'] = false; // default: 0
            whiteList['outline-style'] = false; // default: none
            whiteList['outline-width'] = false; // default: medium
            whiteList['overflow'] = false; // default: depending on individual properties
            whiteList['overflow-wrap'] = false; // default: normal
            whiteList['overflow-x'] = false; // default: visible
            whiteList['overflow-y'] = false; // default: visible
            whiteList['padding'] = true; // default: depending on individual properties
            whiteList['padding-bottom'] = true; // default: 0
            whiteList['padding-left'] = true; // default: 0
            whiteList['padding-right'] = true; // default: 0
            whiteList['padding-top'] = true; // default: 0
            whiteList['page'] = false; // default: auto
            whiteList['page-break-after'] = false; // default: auto
            whiteList['page-break-before'] = false; // default: auto
            whiteList['page-break-inside'] = false; // default: auto
            whiteList['page-policy'] = false; // default: start
            whiteList['pause'] = false; // default: implementation dependent
            whiteList['pause-after'] = false; // default: implementation dependent
            whiteList['pause-before'] = false; // default: implementation dependent
            whiteList['perspective'] = false; // default: none
            whiteList['perspective-origin'] = false; // default: 50% 50%
            whiteList['pitch'] = false; // default: medium
            whiteList['pitch-range'] = false; // default: 50
            whiteList['play-during'] = false; // default: auto
            whiteList['position'] = false; // default: static
            whiteList['presentation-level'] = false; // default: 0
            whiteList['quotes'] = false; // default: text
            whiteList['region-fragment'] = false; // default: auto
            whiteList['resize'] = false; // default: none
            whiteList['rest'] = false; // default: depending on individual properties
            whiteList['rest-after'] = false; // default: none
            whiteList['rest-before'] = false; // default: none
            whiteList['richness'] = false; // default: 50
            whiteList['right'] = false; // default: auto
            whiteList['rotation'] = false; // default: 0
            whiteList['rotation-point'] = false; // default: 50% 50%
            whiteList['ruby-align'] = false; // default: auto
            whiteList['ruby-merge'] = false; // default: separate
            whiteList['ruby-position'] = false; // default: before
            whiteList['shape-image-threshold'] = false; // default: 0.0
            whiteList['shape-outside'] = false; // default: none
            whiteList['shape-margin'] = false; // default: 0
            whiteList['size'] = false; // default: auto
            whiteList['speak'] = false; // default: auto
            whiteList['speak-as'] = false; // default: normal
            whiteList['speak-header'] = false; // default: once
            whiteList['speak-numeral'] = false; // default: continuous
            whiteList['speak-punctuation'] = false; // default: none
            whiteList['speech-rate'] = false; // default: medium
            whiteList['stress'] = false; // default: 50
            whiteList['string-set'] = false; // default: none
            whiteList['tab-size'] = false; // default: 8
            whiteList['table-layout'] = false; // default: auto
            whiteList['text-align'] = true; // default: start
            whiteList['text-align-last'] = true; // default: auto
            whiteList['text-combine-upright'] = true; // default: none
            whiteList['text-decoration'] = true; // default: none
            whiteList['text-decoration-color'] = true; // default: currentColor
            whiteList['text-decoration-line'] = true; // default: none
            whiteList['text-decoration-skip'] = true; // default: objects
            whiteList['text-decoration-style'] = true; // default: solid
            whiteList['text-emphasis'] = true; // default: depending on individual properties
            whiteList['text-emphasis-color'] = true; // default: currentColor
            whiteList['text-emphasis-position'] = true; // default: over right
            whiteList['text-emphasis-style'] = true; // default: none
            whiteList['text-height'] = true; // default: auto
            whiteList['text-indent'] = true; // default: 0
            whiteList['text-justify'] = true; // default: auto
            whiteList['text-orientation'] = true; // default: mixed
            whiteList['text-overflow'] = true; // default: clip
            whiteList['text-shadow'] = true; // default: none
            whiteList['text-space-collapse'] = true; // default: collapse
            whiteList['text-transform'] = true; // default: none
            whiteList['text-underline-position'] = true; // default: auto
            whiteList['text-wrap'] = true; // default: normal
            whiteList['top'] = false; // default: auto
            whiteList['transform'] = false; // default: none
            whiteList['transform-origin'] = false; // default: 50% 50% 0
            whiteList['transform-style'] = false; // default: flat
            whiteList['transition'] = false; // default: depending on individual properties
            whiteList['transition-delay'] = false; // default: 0s
            whiteList['transition-duration'] = false; // default: 0s
            whiteList['transition-property'] = false; // default: all
            whiteList['transition-timing-function'] = false; // default: ease
            whiteList['unicode-bidi'] = false; // default: normal
            whiteList['vertical-align'] = false; // default: baseline
            whiteList['visibility'] = false; // default: visible
            whiteList['voice-balance'] = false; // default: center
            whiteList['voice-duration'] = false; // default: auto
            whiteList['voice-family'] = false; // default: implementation dependent
            whiteList['voice-pitch'] = false; // default: medium
            whiteList['voice-range'] = false; // default: medium
            whiteList['voice-rate'] = false; // default: normal
            whiteList['voice-stress'] = false; // default: normal
            whiteList['voice-volume'] = false; // default: medium
            whiteList['volume'] = false; // default: medium
            whiteList['white-space'] = false; // default: normal
            whiteList['widows'] = false; // default: 2
            whiteList['width'] = true; // default: auto
            whiteList['will-change'] = false; // default: auto
            whiteList['word-break'] = true; // default: normal
            whiteList['word-spacing'] = true; // default: normal
            whiteList['word-wrap'] = true; // default: normal
            whiteList['wrap-flow'] = false; // default: auto
            whiteList['wrap-through'] = false; // default: wrap
            whiteList['writing-mode'] = false; // default: horizontal-tb
            whiteList['z-index'] = false; // default: auto

            return whiteList;
        }


        /**
         * 匹配到白名单上的一个属性时
         *
         * @param {String} name
         * @param {String} value
         * @param {Object} options
         * @return {String}
         */
        function onAttr(name, value, options) {
            // do nothing
        }

        /**
         * 匹配到不在白名单上的一个属性时
         *
         * @param {String} name
         * @param {String} value
         * @param {Object} options
         * @return {String}
         */
        function onIgnoreAttr(name, value, options) {
            // do nothing
        }

        var REGEXP_URL_JAVASCRIPT = /javascript\s*\:/img;

        /**
         * 过滤属性值
         *
         * @param {String} name
         * @param {String} value
         * @return {String}
         */
        function safeAttrValue(name, value) {
            if (REGEXP_URL_JAVASCRIPT.test(value)) return '';
            return value;
        }


        exports.whiteList = getDefaultWhiteList();
        exports.getDefaultWhiteList = getDefaultWhiteList;
        exports.onAttr = onAttr;
        exports.onIgnoreAttr = onIgnoreAttr;
        exports.safeAttrValue = safeAttrValue;

    }, {}], 8: [function (require, module, exports) {
        /**
         * cssfilter
         *
         * @author 老雷<leizongmin@gmail.com>
         */

        var DEFAULT = require('./default');
        var FilterCSS = require('./css');


        /**
         * XSS过滤
         *
         * @param {String} css 要过滤的CSS代码
         * @param {Object} options 选项：whiteList, onAttr, onIgnoreAttr
         * @return {String}
         */
        function filterCSS(html, options) {
            var xss = new FilterCSS(options);
            return xss.process(html);
        }


        // 输出
        exports = module.exports = filterCSS;
        exports.FilterCSS = FilterCSS;
        for (var i in DEFAULT) exports[i] = DEFAULT[i];

        // 在浏览器端使用
        if (typeof window !== 'undefined') {
            window.filterCSS = module.exports;
        }

    }, { "./css": 6, "./default": 7 }], 9: [function (require, module, exports) {
        /**
         * cssfilter
         *
         * @author 老雷<leizongmin@gmail.com>
         */

        var _ = require('./util');


        /**
         * 解析style
         *
         * @param {String} css
         * @param {Function} onAttr 处理属性的函数
         *   参数格式： function (sourcePosition, position, name, value, source)
         * @return {String}
         */
        function parseStyle(css, onAttr) {
            css = _.trimRight(css);
            if (css[css.length - 1] !== ';') css += ';';
            var cssLength = css.length;
            var isParenthesisOpen = false;
            var lastPos = 0;
            var i = 0;
            var retCSS = '';

            function addNewAttr() {
                // 如果没有正常的闭合圆括号，则直接忽略当前属性
                if (!isParenthesisOpen) {
                    var source = _.trim(css.slice(lastPos, i));
                    var j = source.indexOf(':');
                    if (j !== -1) {
                        var name = _.trim(source.slice(0, j));
                        var value = _.trim(source.slice(j + 1));
                        // 必须有属性名称
                        if (name) {
                            var ret = onAttr(lastPos, retCSS.length, name, value, source);
                            if (ret) retCSS += ret + '; ';
                        }
                    }
                }
                lastPos = i + 1;
            }

            for (; i < cssLength; i++) {
                var c = css[i];
                if (c === '/' && css[i + 1] === '*') {
                    // 备注开始
                    var j = css.indexOf('*/', i + 2);
                    // 如果没有正常的备注结束，则后面的部分全部跳过
                    if (j === -1) break;
                    // 直接将当前位置调到备注结尾，并且初始化状态
                    i = j + 1;
                    lastPos = i + 1;
                    isParenthesisOpen = false;
                } else if (c === '(') {
                    isParenthesisOpen = true;
                } else if (c === ')') {
                    isParenthesisOpen = false;
                } else if (c === ';') {
                    if (isParenthesisOpen) {
                        // 在圆括号里面，忽略
                    } else {
                        addNewAttr();
                    }
                } else if (c === '\n') {
                    addNewAttr();
                }
            }

            return _.trim(retCSS);
        }

        module.exports = parseStyle;

    }, { "./util": 10 }], 10: [function (require, module, exports) {
        module.exports = {
            indexOf: function (arr, item) {
                var i, j;
                if (Array.prototype.indexOf) {
                    return arr.indexOf(item);
                }
                for (i = 0, j = arr.length; i < j; i++) {
                    if (arr[i] === item) {
                        return i;
                    }
                }
                return -1;
            },
            forEach: function (arr, fn, scope) {
                var i, j;
                if (Array.prototype.forEach) {
                    return arr.forEach(fn, scope);
                }
                for (i = 0, j = arr.length; i < j; i++) {
                    fn.call(scope, arr[i], i, arr);
                }
            },
            trim: function (str) {
                if (String.prototype.trim) {
                    return str.trim();
                }
                return str.replace(/(^\s*)|(\s*$)/g, '');
            },
            trimRight: function (str) {
                if (String.prototype.trimRight) {
                    return str.trimRight();
                }
                return str.replace(/(\s*$)/g, '');
            }
        };

    }, {}]
}, {}, [2]);;
/*
     _ _      _       _
 ___| (_) ___| | __  (_)___
/ __| | |/ __| |/ /  | / __|
\__ \ | | (__|   < _ | \__ \
|___/_|_|\___|_|\_(_)/ |___/
                   |__/

 Version: 1.5.0
  Author: Ken Wheeler
 Website: http://kenwheeler.github.io
    Docs: http://kenwheeler.github.io/slick
    Repo: http://github.com/kenwheeler/slick
  Issues: http://github.com/kenwheeler/slick/issues

 */
/* global window, document, define, jQuery, setInterval, clearInterval */
(function(factory) {
    'use strict';
    if (typeof define === 'function' && define.amd) {
        define(['jquery'], factory);
    } else if (typeof exports !== 'undefined') {
        module.exports = factory(require('jquery'));
    } else {
        factory(jQuery);
    }

}(function($) {
    'use strict';
    var Slick = window.Slick || {};

    Slick = (function() {

        var instanceUid = 0;

        function Slick(element, settings) {

            var _ = this,
                dataSettings, responsiveSettings, breakpoint;

            _.defaults = {
                accessibility: true,
                adaptiveHeight: false,
                appendArrows: $(element),
                appendDots: $(element),
                arrows: true,
                asNavFor: null,
                prevArrow: '<div class="icon-navi-left-icon iconSVG_Darker slick-prev" data-role="none" aria-label="previous"></div>',
                //nextArrow: '<button type="button" data-role="none" class="slick-next" aria-label="next">Next</button>',
                nextArrow: '<div class="icon-navi-right-icon iconSVG_Darker slick-next" data-role="none" aria-label="next"></div>',
                autoplay: false,
                autoplaySpeed: 3000,
                centerMode: false,
                centerPadding: '50px',
                cssEase: 'ease',
                customPaging: function(slider, i) {
                    return '<button type="button" data-role="none">' + (i + 1) + '</button>';
                },
                dots: false,
                dotsClass: 'slick-dots',
                draggable: true,
                easing: 'linear',
                edgeFriction: 0.35,
                fade: false,
                focusOnSelect: false,
                infinite: true,
                initialSlide: 0,
                lazyLoad: 'ondemand',
                mobileFirst: false,
                pauseOnHover: true,
                pauseOnDotsHover: false,
                respondTo: 'window',
                responsive: null,
                rows: 1,
                rtl: false,
                slide: '',
                slidesPerRow: 1,
                slidesToShow: 1,
                slidesToScroll: 1,
                speed: 500,
                swipe: true,
                swipeToSlide: false,
                touchMove: true,
                touchThreshold: 5,
                useCSS: true,
                variableWidth: false,
                vertical: false,
                verticalSwiping: false,
                waitForAnimate: true
            };

            _.initials = {
                animating: false,
                dragging: false,
                autoPlayTimer: null,
                currentDirection: 0,
                currentLeft: null,
                currentSlide: 0,
                direction: 1,
                $dots: null,
                listWidth: null,
                listHeight: null,
                loadIndex: 0,
                $nextArrow: null,
                $prevArrow: null,
                slideCount: null,
                slideWidth: null,
                $slideTrack: null,
                $slides: null,
                sliding: false,
                slideOffset: 0,
                swipeLeft: null,
                $list: null,
                touchObject: {},
                transformsEnabled: false
            };

            $.extend(_, _.initials);

            _.activeBreakpoint = null;
            _.animType = null;
            _.animProp = null;
            _.breakpoints = [];
            _.breakpointSettings = [];
            _.cssTransitions = false;
            _.hidden = 'hidden';
            _.paused = false;
            _.positionProp = null;
            _.respondTo = null;
            _.rowCount = 1;
            _.shouldClick = true;
            _.$slider = $(element);
            _.$slidesCache = null;
            _.transformType = null;
            _.transitionType = null;
            _.visibilityChange = 'visibilitychange';
            _.windowWidth = 0;
            _.windowTimer = null;

            dataSettings = $(element).data('slick') || {};

            _.options = $.extend({}, _.defaults, dataSettings, settings);

            _.currentSlide = parseInt(_.options.initialSlide);

            _.originalSettings = _.options;
            responsiveSettings = _.options.responsive || null;

            if (responsiveSettings && responsiveSettings.length > -1) {
                _.respondTo = _.options.respondTo || 'window';
                for (breakpoint in responsiveSettings) {
                    if (responsiveSettings.hasOwnProperty(breakpoint)) {
                        _.breakpoints.push(responsiveSettings[
                            breakpoint].breakpoint);
                        _.breakpointSettings[responsiveSettings[
                                breakpoint].breakpoint] =
                            responsiveSettings[breakpoint].settings;
                    }
                }
                _.breakpoints.sort(function(a, b) {
                    if (_.options.mobileFirst === true) {
                        return a - b;
                    } else {
                        return b - a;
                    }
                });
            }

            if (typeof document.mozHidden !== 'undefined') {
                _.hidden = 'mozHidden';
                _.visibilityChange = 'mozvisibilitychange';
            } else if (typeof document.msHidden !== 'undefined') {
                _.hidden = 'msHidden';
                _.visibilityChange = 'msvisibilitychange';
            } else if (typeof document.webkitHidden !== 'undefined') {
                _.hidden = 'webkitHidden';
                _.visibilityChange = 'webkitvisibilitychange';
            }

            _.autoPlay = $.proxy(_.autoPlay, _);
            _.autoPlayClear = $.proxy(_.autoPlayClear, _);
            _.changeSlide = $.proxy(_.changeSlide, _);
            _.clickHandler = $.proxy(_.clickHandler, _);
            _.selectHandler = $.proxy(_.selectHandler, _);
            _.setPosition = $.proxy(_.setPosition, _);
            _.swipeHandler = $.proxy(_.swipeHandler, _);
            _.dragHandler = $.proxy(_.dragHandler, _);
            _.keyHandler = $.proxy(_.keyHandler, _);
            _.autoPlayIterator = $.proxy(_.autoPlayIterator, _);

            _.instanceUid = instanceUid++;

            // A simple way to check for HTML strings
            // Strict HTML recognition (must start with <)
            // Extracted from jQuery v1.11 source
            _.htmlExpr = /^(?:\s*(<[\w\W]+>)[^>]*)$/;

            _.init();

            _.checkResponsive(true);

        }

        return Slick;

    }());

    Slick.prototype.addSlide = Slick.prototype.slickAdd = function(markup, index, addBefore) {

        var _ = this;

        if (typeof(index) === 'boolean') {
            addBefore = index;
            index = null;
        } else if (index < 0 || (index >= _.slideCount)) {
            return false;
        }

        _.unload();

        if (typeof(index) === 'number') {
            if (index === 0 && _.$slides.length === 0) {
                $(markup).appendTo(_.$slideTrack);
            } else if (addBefore) {
                $(markup).insertBefore(_.$slides.eq(index));
            } else {
                $(markup).insertAfter(_.$slides.eq(index));
            }
        } else {
            if (addBefore === true) {
                $(markup).prependTo(_.$slideTrack);
            } else {
                $(markup).appendTo(_.$slideTrack);
            }
        }

        _.$slides = _.$slideTrack.children(this.options.slide);

        _.$slideTrack.children(this.options.slide).detach();

        _.$slideTrack.append(_.$slides);

        _.$slides.each(function(index, element) {
            $(element).attr('data-slick-index', index);
        });

        _.$slidesCache = _.$slides;

        _.reinit();

    };

    Slick.prototype.animateHeight = function() {
        var _ = this;
        if (_.options.slidesToShow === 1 && _.options.adaptiveHeight === true && _.options.vertical === false) {
            var targetHeight = _.$slides.eq(_.currentSlide).outerHeight(true);
            _.$list.animate({
                height: targetHeight
            }, _.options.speed);
        }
    };

    Slick.prototype.animateSlide = function(targetLeft, callback) {

        var animProps = {},
            _ = this;

        _.animateHeight();

        if (_.options.rtl === true && _.options.vertical === false) {
            targetLeft = -targetLeft;
        }
        if (_.transformsEnabled === false) {
            if (_.options.vertical === false) {
                _.$slideTrack.animate({
                    left: targetLeft
                }, _.options.speed, _.options.easing, callback);
            } else {
                _.$slideTrack.animate({
                    top: targetLeft
                }, _.options.speed, _.options.easing, callback);
            }

        } else {

            if (_.cssTransitions === false) {
                if (_.options.rtl === true) {
                    _.currentLeft = -(_.currentLeft);
                }
                $({
                    animStart: _.currentLeft
                }).animate({
                    animStart: targetLeft
                }, {
                    duration: _.options.speed,
                    easing: _.options.easing,
                    step: function(now) {
                        now = Math.ceil(now);
                        if (_.options.vertical === false) {
                            animProps[_.animType] = 'translate(' +
                                now + 'px, 0px)';
                            _.$slideTrack.css(animProps);
                        } else {
                            animProps[_.animType] = 'translate(0px,' +
                                now + 'px)';
                            _.$slideTrack.css(animProps);
                        }
                    },
                    complete: function() {
                        if (callback) {
                            callback.call();
                        }
                    }
                });

            } else {

                _.applyTransition();
                targetLeft = Math.ceil(targetLeft);

                if (_.options.vertical === false) {
                    animProps[_.animType] = 'translate3d(' + targetLeft + 'px, 0px, 0px)';
                } else {
                    animProps[_.animType] = 'translate3d(0px,' + targetLeft + 'px, 0px)';
                }
                _.$slideTrack.css(animProps);

                if (callback) {
                    setTimeout(function() {

                        _.disableTransition();

                        callback.call();
                    }, _.options.speed);
                }

            }

        }

    };

    Slick.prototype.asNavFor = function(index) {
        var _ = this,
            asNavFor = _.options.asNavFor !== null ? $(_.options.asNavFor).slick('getSlick') : null;
        if (asNavFor !== null) asNavFor.slideHandler(index, true);
    };

    Slick.prototype.applyTransition = function(slide) {

        var _ = this,
            transition = {};

        if (_.options.fade === false) {
            transition[_.transitionType] = _.transformType + ' ' + _.options.speed + 'ms ' + _.options.cssEase;
        } else {
            transition[_.transitionType] = 'opacity ' + _.options.speed + 'ms ' + _.options.cssEase;
        }

        if (_.options.fade === false) {
            _.$slideTrack.css(transition);
        } else {
            _.$slides.eq(slide).css(transition);
        }

    };

    Slick.prototype.autoPlay = function() {

        var _ = this;

        if (_.autoPlayTimer) {
            clearInterval(_.autoPlayTimer);
        }

        if (_.slideCount > _.options.slidesToShow && _.paused !== true) {
            _.autoPlayTimer = setInterval(_.autoPlayIterator,
                _.options.autoplaySpeed);
        }

    };

    Slick.prototype.autoPlayClear = function() {

        var _ = this;
        if (_.autoPlayTimer) {
            clearInterval(_.autoPlayTimer);
        }

    };

    Slick.prototype.autoPlayIterator = function() {

        var _ = this;

        if (_.options.infinite === false) {

            if (_.direction === 1) {

                if ((_.currentSlide + 1) === _.slideCount -
                    1) {
                    _.direction = 0;
                }

                _.slideHandler(_.currentSlide + _.options.slidesToScroll);

            } else {

                if ((_.currentSlide - 1 === 0)) {

                    _.direction = 1;

                }

                _.slideHandler(_.currentSlide - _.options.slidesToScroll);

            }

        } else {

            _.slideHandler(_.currentSlide + _.options.slidesToScroll);

        }

    };

    Slick.prototype.buildArrows = function() {

        var _ = this;

        if (_.options.arrows === true && _.slideCount > _.options.slidesToShow) {

            _.$prevArrow = $(_.options.prevArrow);
            _.$nextArrow = $(_.options.nextArrow);

            if (_.htmlExpr.test(_.options.prevArrow)) {
                _.$prevArrow.appendTo(_.options.appendArrows);
            }

            if (_.htmlExpr.test(_.options.nextArrow)) {
                _.$nextArrow.appendTo(_.options.appendArrows);
            }

            if (_.options.infinite !== true) {
                _.$prevArrow.addClass('slick-disabled');
            }

        }

    };

    Slick.prototype.buildDots = function() {

        var _ = this,
            i, dotString;

        if (_.options.dots === true && _.slideCount > _.options.slidesToShow) {

            dotString = '<ul class="' + _.options.dotsClass + '">';

            for (i = 0; i <= _.getDotCount(); i += 1) {
                dotString += '<li>' + _.options.customPaging.call(this, _, i) + '</li>';
            }

            dotString += '</ul>';

            _.$dots = $(dotString).appendTo(
                _.options.appendDots);

            _.$dots.find('li').first().addClass('slick-active').attr('aria-hidden', 'false');

        }

    };

    Slick.prototype.buildOut = function() {

        var _ = this;

        _.$slides = _.$slider.children(
            ':not(.slick-cloned)').addClass(
            'slick-slide');
        _.slideCount = _.$slides.length;

        _.$slides.each(function(index, element) {
            $(element).attr('data-slick-index', index);
        });

        _.$slidesCache = _.$slides;

        _.$slider.addClass('slick-slider');

        _.$slideTrack = (_.slideCount === 0) ?
            $('<div class="slick-track"/>').appendTo(_.$slider) :
            _.$slides.wrapAll('<div class="slick-track"/>').parent();

        _.$list = _.$slideTrack.wrap(
            '<div aria-live="polite" class="slick-list"/>').parent();
        _.$slideTrack.css('opacity', 0);

        if (_.options.centerMode === true || _.options.swipeToSlide === true) {
            _.options.slidesToScroll = 1;
        }

        $('img[data-lazy]', _.$slider).not('[src]').addClass('slick-loading');

        _.setupInfinite();

        _.buildArrows();

        _.buildDots();

        _.updateDots();

        if (_.options.accessibility === true) {
            _.$list.prop('tabIndex', 0);
        }

        _.setSlideClasses(typeof this.currentSlide === 'number' ? this.currentSlide : 0);

        if (_.options.draggable === true) {
            _.$list.addClass('draggable');
        }

    };

    Slick.prototype.buildRows = function() {

        var _ = this, a, b, c, newSlides, numOfSlides, originalSlides,slidesPerSection;

        newSlides = document.createDocumentFragment();
        originalSlides = _.$slider.children();

        if(_.options.rows > 1) {
            slidesPerSection = _.options.slidesPerRow * _.options.rows;
            numOfSlides = Math.ceil(
                originalSlides.length / slidesPerSection
            );

            for(a = 0; a < numOfSlides; a++){
                var slide = document.createElement('div');
                for(b = 0; b < _.options.rows; b++) {
                    var row = document.createElement('div');
                    for(c = 0; c < _.options.slidesPerRow; c++) {
                        var target = (a * slidesPerSection + ((b * _.options.slidesPerRow) + c));
                        if (originalSlides.get(target)) {
                            row.appendChild(originalSlides.get(target));
                        }
                    }
                    slide.appendChild(row);
                }
                newSlides.appendChild(slide);
            };
            _.$slider.html(newSlides);
            _.$slider.children().children().children()
                .width((100 / _.options.slidesPerRow) + "%")
                .css({'display': 'inline-block'});
        };

    };

    Slick.prototype.checkResponsive = function(initial) {

        var _ = this,
            breakpoint, targetBreakpoint, respondToWidth;
        var sliderWidth = _.$slider.width();
        var windowWidth = window.innerWidth || $(window).width();
        if (_.respondTo === 'window') {
            respondToWidth = windowWidth;
        } else if (_.respondTo === 'slider') {
            respondToWidth = sliderWidth;
        } else if (_.respondTo === 'min') {
            respondToWidth = Math.min(windowWidth, sliderWidth);
        }

        if (_.originalSettings.responsive && _.originalSettings
            .responsive.length > -1 && _.originalSettings.responsive !== null) {

            targetBreakpoint = null;

            for (breakpoint in _.breakpoints) {
                if (_.breakpoints.hasOwnProperty(breakpoint)) {
                    if (_.originalSettings.mobileFirst === false) {
                        if (respondToWidth < _.breakpoints[breakpoint]) {
                            targetBreakpoint = _.breakpoints[breakpoint];
                        }
                    } else {
                        if (respondToWidth > _.breakpoints[breakpoint]) {
                            targetBreakpoint = _.breakpoints[breakpoint];
                        }
                    }
                }
            }

            if (targetBreakpoint !== null) {
                if (_.activeBreakpoint !== null) {
                    if (targetBreakpoint !== _.activeBreakpoint) {
                        _.activeBreakpoint =
                            targetBreakpoint;
                        if (_.breakpointSettings[targetBreakpoint] === 'unslick') {
                            _.unslick();
                        } else {
                            _.options = $.extend({}, _.originalSettings,
                                _.breakpointSettings[
                                    targetBreakpoint]);
                            if (initial === true)
                                _.currentSlide = _.options.initialSlide;
                            _.refresh();
                        }
                    }
                } else {
                    _.activeBreakpoint = targetBreakpoint;
                    if (_.breakpointSettings[targetBreakpoint] === 'unslick') {
                        _.unslick();
                    } else {
                        _.options = $.extend({}, _.originalSettings,
                            _.breakpointSettings[
                                targetBreakpoint]);
                        if (initial === true)
                            _.currentSlide = _.options.initialSlide;
                        _.refresh();
                    }
                }
            } else {
                if (_.activeBreakpoint !== null) {
                    _.activeBreakpoint = null;
                    _.options = _.originalSettings;
                    if (initial === true)
                        _.currentSlide = _.options.initialSlide;
                    _.refresh();
                }
            }

        }

    };

    Slick.prototype.changeSlide = function(event, dontAnimate) {

        var _ = this,
            $target = $(event.target),
            indexOffset, slideOffset, unevenOffset;

        // If target is a link, prevent default action.
        $target.is('a') && event.preventDefault();

        unevenOffset = (_.slideCount % _.options.slidesToScroll !== 0);
        indexOffset = unevenOffset ? 0 : (_.slideCount - _.currentSlide) % _.options.slidesToScroll;

        switch (event.data.message) {

            case 'previous':
                slideOffset = indexOffset === 0 ? _.options.slidesToScroll : _.options.slidesToShow - indexOffset;
                if (_.slideCount > _.options.slidesToShow) {
                    _.slideHandler(_.currentSlide - slideOffset, false, dontAnimate);
                }
                break;

            case 'next':
                slideOffset = indexOffset === 0 ? _.options.slidesToScroll : indexOffset;
                if (_.slideCount > _.options.slidesToShow) {
                    _.slideHandler(_.currentSlide + slideOffset, false, dontAnimate);
                }
                break;

            case 'index':
                var index = event.data.index === 0 ? 0 :
                    event.data.index || $(event.target).parent().index() * _.options.slidesToScroll;

                _.slideHandler(_.checkNavigable(index), false, dontAnimate);
                break;

            default:
                return;
        }

    };

    Slick.prototype.checkNavigable = function(index) {

        var _ = this,
            navigables, prevNavigable;

        navigables = _.getNavigableIndexes();
        prevNavigable = 0;
        if (index > navigables[navigables.length - 1]) {
            index = navigables[navigables.length - 1];
        } else {
            for (var n in navigables) {
                if (index < navigables[n]) {
                    index = prevNavigable;
                    break;
                }
                prevNavigable = navigables[n];
            }
        }

        return index;
    };

    Slick.prototype.cleanUpEvents = function() {

        var _ = this;

        if (_.options.dots === true && _.slideCount > _.options.slidesToShow) {
            $('li', _.$dots).off('click.slick', _.changeSlide);
        }

        if (_.options.dots === true && _.options.pauseOnDotsHover === true && _.options.autoplay === true) {
            $('li', _.$dots)
                .off('mouseenter.slick', _.setPaused.bind(_, true))
                .off('mouseleave.slick', _.setPaused.bind(_, false));
        }

        if (_.options.arrows === true && _.slideCount > _.options.slidesToShow) {
            _.$prevArrow && _.$prevArrow.off('click.slick', _.changeSlide);
            _.$nextArrow && _.$nextArrow.off('click.slick', _.changeSlide);
        }

        _.$list.off('touchstart.slick mousedown.slick', _.swipeHandler);
        _.$list.off('touchmove.slick mousemove.slick', _.swipeHandler);
        _.$list.off('touchend.slick mouseup.slick', _.swipeHandler);
        _.$list.off('touchcancel.slick mouseleave.slick', _.swipeHandler);

        _.$list.off('click.slick', _.clickHandler);

        if (_.options.autoplay === true) {
            $(document).off(_.visibilityChange, _.visibility);
        }

        _.$list.off('mouseenter.slick', _.setPaused.bind(_, true));
        _.$list.off('mouseleave.slick', _.setPaused.bind(_, false));

        if (_.options.accessibility === true) {
            _.$list.off('keydown.slick', _.keyHandler);
        }

        if (_.options.focusOnSelect === true) {
            $(_.$slideTrack).children().off('click.slick', _.selectHandler);
        }

        $(window).off('orientationchange.slick.slick-' + _.instanceUid, _.orientationChange);

        $(window).off('resize.slick.slick-' + _.instanceUid, _.resize);

        $('[draggable!=true]', _.$slideTrack).off('dragstart', _.preventDefault);

        $(window).off('load.slick.slick-' + _.instanceUid, _.setPosition);
        $(document).off('ready.slick.slick-' + _.instanceUid, _.setPosition);
    };

    Slick.prototype.cleanUpRows = function() {

        var _ = this, originalSlides;

        if(_.options.rows > 1) {
            originalSlides = _.$slides.children().children();
            originalSlides.removeAttr('style');
            _.$slider.html(originalSlides);
        }

    };

    Slick.prototype.clickHandler = function(event) {

        var _ = this;

        if (_.shouldClick === false) {
            event.stopImmediatePropagation();
            event.stopPropagation();
            event.preventDefault();
        }

    };

    Slick.prototype.destroy = function() {

        var _ = this;

        _.autoPlayClear();

        _.touchObject = {};

        _.cleanUpEvents();

        $('.slick-cloned', _.$slider).remove();

        if (_.$dots) {
            _.$dots.remove();
        }
        if (_.$prevArrow && (typeof _.options.prevArrow !== 'object')) {
            _.$prevArrow.remove();
        }
        if (_.$nextArrow && (typeof _.options.nextArrow !== 'object')) {
            _.$nextArrow.remove();
        }

        if (_.$slides) {
            _.$slides.removeClass('slick-slide slick-active slick-center slick-visible')
                .attr('aria-hidden', 'true')
                .removeAttr('data-slick-index')
                .css({
                    position: '',
                    left: '',
                    top: '',
                    zIndex: '',
                    opacity: '',
                    width: ''
                });

            _.$slider.html(_.$slides);
        }

        _.cleanUpRows();

        _.$slider.removeClass('slick-slider');
        _.$slider.removeClass('slick-initialized');

    };

    Slick.prototype.disableTransition = function(slide) {

        var _ = this,
            transition = {};

        transition[_.transitionType] = '';

        if (_.options.fade === false) {
            _.$slideTrack.css(transition);
        } else {
            _.$slides.eq(slide).css(transition);
        }

    };

    Slick.prototype.fadeSlide = function(slideIndex, callback) {

        var _ = this;

        if (_.cssTransitions === false) {

            _.$slides.eq(slideIndex).css({
                zIndex: 1000
            });

            _.$slides.eq(slideIndex).animate({
                opacity: 1
            }, _.options.speed, _.options.easing, callback);

        } else {

            _.applyTransition(slideIndex);

            _.$slides.eq(slideIndex).css({
                opacity: 1,
                zIndex: 1000
            });

            if (callback) {
                setTimeout(function() {

                    _.disableTransition(slideIndex);

                    callback.call();
                }, _.options.speed);
            }

        }

    };

    Slick.prototype.filterSlides = Slick.prototype.slickFilter = function(filter) {

        var _ = this;

        if (filter !== null) {

            _.unload();

            _.$slideTrack.children(this.options.slide).detach();

            _.$slidesCache.filter(filter).appendTo(_.$slideTrack);

            _.reinit();

        }

    };

    Slick.prototype.getCurrent = Slick.prototype.slickCurrentSlide = function() {

        var _ = this;
        return _.currentSlide;

    };

    Slick.prototype.getDotCount = function() {

        var _ = this;

        var breakPoint = 0;
        var counter = 0;
        var pagerQty = 0;

        if (_.options.infinite === true) {
            pagerQty = Math.ceil(_.slideCount / _.options.slidesToScroll);
        } else if (_.options.centerMode === true) {
            pagerQty = _.slideCount;
        } else {
            while (breakPoint < _.slideCount) {
                ++pagerQty;
                breakPoint = counter + _.options.slidesToShow;
                counter += _.options.slidesToScroll <= _.options.slidesToShow ? _.options.slidesToScroll : _.options.slidesToShow;
            }
        }

        return pagerQty - 1;

    };

    Slick.prototype.getLeft = function(slideIndex) {

        var _ = this,
            targetLeft,
            verticalHeight,
            verticalOffset = 0,
            targetSlide;

        _.slideOffset = 0;
        verticalHeight = _.$slides.first().outerHeight();

        if (_.options.infinite === true) {
            if (_.slideCount > _.options.slidesToShow) {
                _.slideOffset = (_.slideWidth * _.options.slidesToShow) * -1;
                verticalOffset = (verticalHeight * _.options.slidesToShow) * -1;
            }
            if (_.slideCount % _.options.slidesToScroll !== 0) {
                if (slideIndex + _.options.slidesToScroll > _.slideCount && _.slideCount > _.options.slidesToShow) {
                    if (slideIndex > _.slideCount) {
                        _.slideOffset = ((_.options.slidesToShow - (slideIndex - _.slideCount)) * _.slideWidth) * -1;
                        verticalOffset = ((_.options.slidesToShow - (slideIndex - _.slideCount)) * verticalHeight) * -1;
                    } else {
                        _.slideOffset = ((_.slideCount % _.options.slidesToScroll) * _.slideWidth) * -1;
                        verticalOffset = ((_.slideCount % _.options.slidesToScroll) * verticalHeight) * -1;
                    }
                }
            }
        } else {
            if (slideIndex + _.options.slidesToShow > _.slideCount) {
                _.slideOffset = ((slideIndex + _.options.slidesToShow) - _.slideCount) * _.slideWidth;
                verticalOffset = ((slideIndex + _.options.slidesToShow) - _.slideCount) * verticalHeight;
            }
        }

        if (_.slideCount <= _.options.slidesToShow) {
            _.slideOffset = 0;
            verticalOffset = 0;
        }

        if (_.options.centerMode === true && _.options.infinite === true) {
            _.slideOffset += _.slideWidth * Math.floor(_.options.slidesToShow / 2) - _.slideWidth;
        } else if (_.options.centerMode === true) {
            _.slideOffset = 0;
            _.slideOffset += _.slideWidth * Math.floor(_.options.slidesToShow / 2);
        }

        if (_.options.vertical === false) {
            targetLeft = ((slideIndex * _.slideWidth) * -1) + _.slideOffset;
        } else {
            targetLeft = ((slideIndex * verticalHeight) * -1) + verticalOffset;
        }

        if (_.options.variableWidth === true) {

            if (_.slideCount <= _.options.slidesToShow || _.options.infinite === false) {
                targetSlide = _.$slideTrack.children('.slick-slide').eq(slideIndex);
            } else {
                targetSlide = _.$slideTrack.children('.slick-slide').eq(slideIndex + _.options.slidesToShow);
            }

            targetLeft = targetSlide[0] ? targetSlide[0].offsetLeft * -1 : 0;

            if (_.options.centerMode === true) {
                if (_.options.infinite === false) {
                    targetSlide = _.$slideTrack.children('.slick-slide').eq(slideIndex);
                } else {
                    targetSlide = _.$slideTrack.children('.slick-slide').eq(slideIndex + _.options.slidesToShow + 1);
                }
                targetLeft = targetSlide[0] ? targetSlide[0].offsetLeft * -1 : 0;
                targetLeft += (_.$list.width() - targetSlide.outerWidth()) / 2;
            }
        }

        return targetLeft;

    };

    Slick.prototype.getOption = Slick.prototype.slickGetOption = function(option) {

        var _ = this;

        return _.options[option];

    };

    Slick.prototype.getNavigableIndexes = function() {

        var _ = this,
            breakPoint = 0,
            counter = 0,
            indexes = [],
            max;

        if (_.options.infinite === false) {
            max = _.slideCount - _.options.slidesToShow + 1;
            if (_.options.centerMode === true) max = _.slideCount;
        } else {
            breakPoint = _.options.slidesToScroll * -1;
            counter = _.options.slidesToScroll * -1;
            max = _.slideCount * 2;
        }

        while (breakPoint < max) {
            indexes.push(breakPoint);
            breakPoint = counter + _.options.slidesToScroll;
            counter += _.options.slidesToScroll <= _.options.slidesToShow ? _.options.slidesToScroll : _.options.slidesToShow;
        }

        return indexes;

    };

    Slick.prototype.getSlick = function() {

        return this;

    };

    Slick.prototype.getSlideCount = function() {

        var _ = this,
            slidesTraversed, swipedSlide, centerOffset;

        centerOffset = _.options.centerMode === true ? _.slideWidth * Math.floor(_.options.slidesToShow / 2) : 0;

        if (_.options.swipeToSlide === true) {
            _.$slideTrack.find('.slick-slide').each(function(index, slide) {
                if (slide.offsetLeft - centerOffset + ($(slide).outerWidth() / 2) > (_.swipeLeft * -1)) {
                    swipedSlide = slide;
                    return false;
                }
            });

            slidesTraversed = Math.abs($(swipedSlide).attr('data-slick-index') - _.currentSlide) || 1;

            return slidesTraversed;

        } else {
            return _.options.slidesToScroll;
        }

    };

    Slick.prototype.goTo = Slick.prototype.slickGoTo = function(slide, dontAnimate) {

        var _ = this;

        _.changeSlide({
            data: {
                message: 'index',
                index: parseInt(slide)
            }
        }, dontAnimate);

    };

    Slick.prototype.init = function() {

        var _ = this;

        if (!$(_.$slider).hasClass('slick-initialized')) {

            $(_.$slider).addClass('slick-initialized');
            _.buildRows();
            _.buildOut();
            _.setProps();
            _.startLoad();
            _.loadSlider();
            _.initializeEvents();
            _.updateArrows();
            _.updateDots();
        }

        _.$slider.trigger('init', [_]);

    };

    Slick.prototype.initArrowEvents = function() {

        var _ = this;

        if (_.options.arrows === true && _.slideCount > _.options.slidesToShow) {
            _.$prevArrow.on('click.slick', {
                message: 'previous'
            }, _.changeSlide);
            _.$nextArrow.on('click.slick', {
                message: 'next'
            }, _.changeSlide);
        }

    };

    Slick.prototype.initDotEvents = function() {

        var _ = this;

        if (_.options.dots === true && _.slideCount > _.options.slidesToShow) {
            $('li', _.$dots).on('click.slick', {
                message: 'index'
            }, _.changeSlide);
        }

        if (_.options.dots === true && _.options.pauseOnDotsHover === true && _.options.autoplay === true) {
            $('li', _.$dots)
                .on('mouseenter.slick', _.setPaused.bind(_, true))
                .on('mouseleave.slick', _.setPaused.bind(_, false));
        }

    };

    Slick.prototype.initializeEvents = function() {

        var _ = this;

        _.initArrowEvents();

        _.initDotEvents();

        _.$list.on('touchstart.slick mousedown.slick', {
            action: 'start'
        }, _.swipeHandler);
        _.$list.on('touchmove.slick mousemove.slick', {
            action: 'move'
        }, _.swipeHandler);
        _.$list.on('touchend.slick mouseup.slick', {
            action: 'end'
        }, _.swipeHandler);
        _.$list.on('touchcancel.slick mouseleave.slick', {
            action: 'end'
        }, _.swipeHandler);

        _.$list.on('click.slick', _.clickHandler);

        if (_.options.autoplay === true) {
            $(document).on(_.visibilityChange, _.visibility.bind(_));
        }

        _.$list.on('mouseenter.slick', _.setPaused.bind(_, true));
        _.$list.on('mouseleave.slick', _.setPaused.bind(_, false));

        if (_.options.accessibility === true) {
            _.$list.on('keydown.slick', _.keyHandler);
        }

        if (_.options.focusOnSelect === true) {
            $(_.$slideTrack).children().on('click.slick', _.selectHandler);
        }

        $(window).on('orientationchange.slick.slick-' + _.instanceUid, _.orientationChange.bind(_));

        $(window).on('resize.slick.slick-' + _.instanceUid, _.resize.bind(_));

        $('[draggable!=true]', _.$slideTrack).on('dragstart', _.preventDefault);

        $(window).on('load.slick.slick-' + _.instanceUid, _.setPosition);
        $(document).on('ready.slick.slick-' + _.instanceUid, _.setPosition);

    };

    Slick.prototype.initUI = function() {

        var _ = this;

        if (_.options.arrows === true && _.slideCount > _.options.slidesToShow) {

            _.$prevArrow.show();
            _.$nextArrow.show();

        }

        if (_.options.dots === true && _.slideCount > _.options.slidesToShow) {

            _.$dots.show();

        }

        if (_.options.autoplay === true) {

            _.autoPlay();

        }

    };

    Slick.prototype.keyHandler = function(event) {

        var _ = this;

        if (event.keyCode === 37 && _.options.accessibility === true) {
            _.changeSlide({
                data: {
                    message: 'previous'
                }
            });
        } else if (event.keyCode === 39 && _.options.accessibility === true) {
            _.changeSlide({
                data: {
                    message: 'next'
                }
            });
        }

    };

    Slick.prototype.lazyLoad = function() {

        var _ = this,
            loadRange, cloneRange, rangeStart, rangeEnd;

        function loadImages(imagesScope) {
            $('img[data-lazy]', imagesScope).each(function() {
                var image = $(this),
                    imageSource = $(this).attr('data-lazy'),
                    imageToLoad = document.createElement('img');

                imageToLoad.onload = function() {
                    image.animate({
                        opacity: 1
                    }, 200);
                };
                imageToLoad.src = imageSource;

                image
                    .css({
                        opacity: 0
                    })
                    .attr('src', imageSource)
                    .removeAttr('data-lazy')
                    .removeClass('slick-loading');
            });
        }

        if (_.options.centerMode === true) {
            if (_.options.infinite === true) {
                rangeStart = _.currentSlide + (_.options.slidesToShow / 2 + 1);
                rangeEnd = rangeStart + _.options.slidesToShow + 2;
            } else {
                rangeStart = Math.max(0, _.currentSlide - (_.options.slidesToShow / 2 + 1));
                rangeEnd = 2 + (_.options.slidesToShow / 2 + 1) + _.currentSlide;
            }
        } else {
            rangeStart = _.options.infinite ? _.options.slidesToShow + _.currentSlide : _.currentSlide;
            rangeEnd = rangeStart + _.options.slidesToShow;
            if (_.options.fade === true) {
                if (rangeStart > 0) rangeStart--;
                if (rangeEnd <= _.slideCount) rangeEnd++;
            }
        }

        loadRange = _.$slider.find('.slick-slide').slice(rangeStart, rangeEnd);
        loadImages(loadRange);

        if (_.slideCount <= _.options.slidesToShow) {
            cloneRange = _.$slider.find('.slick-slide');
            loadImages(cloneRange);
        } else
        if (_.currentSlide >= _.slideCount - _.options.slidesToShow) {
            cloneRange = _.$slider.find('.slick-cloned').slice(0, _.options.slidesToShow);
            loadImages(cloneRange);
        } else if (_.currentSlide === 0) {
            cloneRange = _.$slider.find('.slick-cloned').slice(_.options.slidesToShow * -1);
            loadImages(cloneRange);
        }

    };

    Slick.prototype.loadSlider = function() {

        var _ = this;

        _.setPosition();

        _.$slideTrack.css({
            opacity: 1
        });

        _.$slider.removeClass('slick-loading');

        _.initUI();

        if (_.options.lazyLoad === 'progressive') {
            _.progressiveLazyLoad();
        }

    };

    Slick.prototype.next = Slick.prototype.slickNext = function() {

        var _ = this;

        _.changeSlide({
            data: {
                message: 'next'
            }
        });

    };

    Slick.prototype.orientationChange = function() {

        var _ = this;

        _.checkResponsive();
        _.setPosition();

    };

    Slick.prototype.pause = Slick.prototype.slickPause = function() {

        var _ = this;

        _.autoPlayClear();
        _.paused = true;

    };

    Slick.prototype.play = Slick.prototype.slickPlay = function() {

        var _ = this;

        _.paused = false;
        _.autoPlay();

    };

    Slick.prototype.postSlide = function (index) {
        var _ = this;

        _.$slider.trigger('afterChange', [_, index]);

        _.animating = false;

        _.setPosition();

        _.swipeLeft = null;

        if (_.options.autoplay === true && _.paused === false) {
            _.autoPlay();
        }

    };

    Slick.prototype.prev = Slick.prototype.slickPrev = function() {

        var _ = this;

        _.changeSlide({
            data: {
                message: 'previous'
            }
        });

    };

    Slick.prototype.preventDefault = function(e) {
        e.preventDefault();
    };

    Slick.prototype.progressiveLazyLoad = function() {

        var _ = this,
            imgCount, targetImage;

        imgCount = $('img[data-lazy]', _.$slider).length;

        if (imgCount > 0) {
            targetImage = $('img[data-lazy]', _.$slider).first();
            targetImage.attr('src', targetImage.attr('data-lazy')).removeClass('slick-loading').load(function() {
                    targetImage.removeAttr('data-lazy');
                    _.progressiveLazyLoad();

                    if (_.options.adaptiveHeight === true) {
                        _.setPosition();
                    }
                })
                .error(function() {
                    targetImage.removeAttr('data-lazy');
                    _.progressiveLazyLoad();
                });
        }

    };

    Slick.prototype.refresh = function() {

        var _ = this,
            currentSlide = _.currentSlide;

        _.destroy();

        $.extend(_, _.initials);

        _.init();

        _.changeSlide({
            data: {
                message: 'index',
                index: currentSlide
            }
        }, false);

    };

    Slick.prototype.reinit = function() {

        var _ = this;

        _.$slides = _.$slideTrack.children(_.options.slide).addClass(
            'slick-slide');

        _.slideCount = _.$slides.length;

        if (_.currentSlide >= _.slideCount && _.currentSlide !== 0) {
            _.currentSlide = _.currentSlide - _.options.slidesToScroll;
        }

        if (_.slideCount <= _.options.slidesToShow) {
            _.currentSlide = 0;
        }

        _.setProps();

        _.setupInfinite();

        _.buildArrows();

        _.updateArrows();

        _.initArrowEvents();

        _.buildDots();

        _.updateDots();

        _.initDotEvents();

        if (_.options.focusOnSelect === true) {
            $(_.$slideTrack).children().on('click.slick', _.selectHandler);
        }

        _.setSlideClasses(0);

        _.setPosition();

        _.$slider.trigger('reInit', [_]);

    };

    Slick.prototype.resize = function() {

        var _ = this;

        if ($(window).width() !== _.windowWidth) {
            clearTimeout(_.windowDelay);
            _.windowDelay = window.setTimeout(function() {
                _.windowWidth = $(window).width();
                _.checkResponsive();
                _.setPosition();
            }, 50);
        }
    };

    Slick.prototype.removeSlide = Slick.prototype.slickRemove = function(index, removeBefore, removeAll) {

        var _ = this;

        if (typeof(index) === 'boolean') {
            removeBefore = index;
            index = removeBefore === true ? 0 : _.slideCount - 1;
        } else {
            index = removeBefore === true ? --index : index;
        }

        if (_.slideCount < 1 || index < 0 || index > _.slideCount - 1) {
            return false;
        }

        _.unload();

        if (removeAll === true) {
            _.$slideTrack.children().remove();
        } else {
            _.$slideTrack.children(this.options.slide).eq(index).remove();
        }

        _.$slides = _.$slideTrack.children(this.options.slide);

        _.$slideTrack.children(this.options.slide).detach();

        _.$slideTrack.append(_.$slides);

        _.$slidesCache = _.$slides;

        _.reinit();

    };

    Slick.prototype.setCSS = function(position) {

        var _ = this,
            positionProps = {},
            x, y;

        if (_.options.rtl === true) {
            position = -position;
        }
        x = _.positionProp == 'left' ? Math.ceil(position) + 'px' : '0px';
        y = _.positionProp == 'top' ? Math.ceil(position) + 'px' : '0px';

        positionProps[_.positionProp] = position;

        if (_.transformsEnabled === false) {
            _.$slideTrack.css(positionProps);
        } else {
            positionProps = {};
            if (_.cssTransitions === false) {
                positionProps[_.animType] = 'translate(' + x + ', ' + y + ')';
                _.$slideTrack.css(positionProps);
            } else {
                positionProps[_.animType] = 'translate3d(' + x + ', ' + y + ', 0px)';
                _.$slideTrack.css(positionProps);
            }
        }

    };

    Slick.prototype.setDimensions = function() {

        var _ = this;

        if (_.options.vertical === false) {
            if (_.options.centerMode === true) {
                _.$list.css({
                    padding: ('0px ' + _.options.centerPadding)
                });
            }
        } else {
            _.$list.height(_.$slides.first().outerHeight(true) * _.options.slidesToShow);
            if (_.options.centerMode === true) {
                _.$list.css({
                    padding: (_.options.centerPadding + ' 0px')
                });
            }
        }

        _.listWidth = _.$list.width();
        _.listHeight = _.$list.height();


        if (_.options.vertical === false && _.options.variableWidth === false) {
            _.slideWidth = Math.ceil(_.listWidth / _.options.slidesToShow);
            _.$slideTrack.width(Math.ceil((_.slideWidth * _.$slideTrack.children('.slick-slide').length)));

        } else if (_.options.variableWidth === true) {
            _.$slideTrack.width(5000 * _.slideCount);
        } else {
            _.slideWidth = Math.ceil(_.listWidth);
            _.$slideTrack.height(Math.ceil((_.$slides.first().outerHeight(true) * _.$slideTrack.children('.slick-slide').length)));
        }

        var offset = _.$slides.first().outerWidth(true) - _.$slides.first().width();
        if (_.options.variableWidth === false) _.$slideTrack.children('.slick-slide').width(_.slideWidth - offset);

    };

    Slick.prototype.setFade = function() {

        var _ = this,
            targetLeft;

        _.$slides.each(function(index, element) {
            targetLeft = (_.slideWidth * index) * -1;
            if (_.options.rtl === true) {
                $(element).css({
                    position: 'relative',
                    right: targetLeft,
                    top: 0,
                    zIndex: 800,
                    opacity: 0
                });
            } else {
                $(element).css({
                    position: 'relative',
                    left: targetLeft,
                    top: 0,
                    zIndex: 800,
                    opacity: 0
                });
            }
        });

        _.$slides.eq(_.currentSlide).css({
            zIndex: 900,
            opacity: 1
        });

    };

    Slick.prototype.setHeight = function() {

        var _ = this;

        if (_.options.slidesToShow === 1 && _.options.adaptiveHeight === true && _.options.vertical === false) {
            var targetHeight = _.$slides.eq(_.currentSlide).outerHeight(true);
            _.$list.css('height', targetHeight);
        }

    };

    Slick.prototype.setOption = Slick.prototype.slickSetOption = function(option, value, refresh) {

        var _ = this;
        _.options[option] = value;

        if (refresh === true) {
            _.unload();
            _.reinit();
        }

    };

    Slick.prototype.setPosition = function() {

        var _ = this;

        _.setDimensions();

        _.setHeight();

        if (_.options.fade === false) {
            _.setCSS(_.getLeft(_.currentSlide));
        } else {
            _.setFade();
        }

        _.$slider.trigger('setPosition', [_]);

    };

    Slick.prototype.setProps = function() {

        var _ = this,
            bodyStyle = document.body.style;

        _.positionProp = _.options.vertical === true ? 'top' : 'left';

        if (_.positionProp === 'top') {
            _.$slider.addClass('slick-vertical');
        } else {
            _.$slider.removeClass('slick-vertical');
        }

        if (bodyStyle.WebkitTransition !== undefined ||
            bodyStyle.MozTransition !== undefined ||
            bodyStyle.msTransition !== undefined) {
            if (_.options.useCSS === true) {
                _.cssTransitions = true;
            }
        }

        if (bodyStyle.OTransform !== undefined) {
            _.animType = 'OTransform';
            _.transformType = '-o-transform';
            _.transitionType = 'OTransition';
            if (bodyStyle.perspectiveProperty === undefined && bodyStyle.webkitPerspective === undefined) _.animType = false;
        }
        if (bodyStyle.MozTransform !== undefined) {
            _.animType = 'MozTransform';
            _.transformType = '-moz-transform';
            _.transitionType = 'MozTransition';
            if (bodyStyle.perspectiveProperty === undefined && bodyStyle.MozPerspective === undefined) _.animType = false;
        }
        if (bodyStyle.webkitTransform !== undefined) {
            _.animType = 'webkitTransform';
            _.transformType = '-webkit-transform';
            _.transitionType = 'webkitTransition';
            if (bodyStyle.perspectiveProperty === undefined && bodyStyle.webkitPerspective === undefined) _.animType = false;
        }
        if (bodyStyle.msTransform !== undefined) {
            _.animType = 'msTransform';
            _.transformType = '-ms-transform';
            _.transitionType = 'msTransition';
            if (bodyStyle.msTransform === undefined) _.animType = false;
        }
        if (bodyStyle.transform !== undefined && _.animType !== false) {
            _.animType = 'transform';
            _.transformType = 'transform';
            _.transitionType = 'transition';
        }
        _.transformsEnabled = (_.animType !== null && _.animType !== false);

    };


    Slick.prototype.setSlideClasses = function(index) {

        var _ = this,
            centerOffset, allSlides, indexOffset, remainder;

        _.$slider.find('.slick-slide').removeClass('slick-active').attr('aria-hidden', 'true').removeClass('slick-center');
        allSlides = _.$slider.find('.slick-slide');

        if (_.options.centerMode === true) {

            centerOffset = Math.floor(_.options.slidesToShow / 2);

            if (_.options.infinite === true) {

                if (index >= centerOffset && index <= (_.slideCount - 1) - centerOffset) {
                    _.$slides.slice(index - centerOffset, index + centerOffset + 1).addClass('slick-active').attr('aria-hidden', 'false');
                } else {
                    indexOffset = _.options.slidesToShow + index;
                    allSlides.slice(indexOffset - centerOffset + 1, indexOffset + centerOffset + 2).addClass('slick-active').attr('aria-hidden', 'false');
                }

                if (index === 0) {
                    allSlides.eq(allSlides.length - 1 - _.options.slidesToShow).addClass('slick-center');
                } else if (index === _.slideCount - 1) {
                    allSlides.eq(_.options.slidesToShow).addClass('slick-center');
                }

            }

            _.$slides.eq(index).addClass('slick-center');

        } else {

            if (index >= 0 && index <= (_.slideCount - _.options.slidesToShow)) {
                _.$slides.slice(index, index + _.options.slidesToShow).addClass('slick-active').attr('aria-hidden', 'false');
            } else if (allSlides.length <= _.options.slidesToShow) {
                allSlides.addClass('slick-active').attr('aria-hidden', 'false');
            } else {
                remainder = _.slideCount % _.options.slidesToShow;
                indexOffset = _.options.infinite === true ? _.options.slidesToShow + index : index;
                if (_.options.slidesToShow == _.options.slidesToScroll && (_.slideCount - index) < _.options.slidesToShow) {
                    allSlides.slice(indexOffset - (_.options.slidesToShow - remainder), indexOffset + remainder).addClass('slick-active').attr('aria-hidden', 'false');
                } else {
                    allSlides.slice(indexOffset, indexOffset + _.options.slidesToShow).addClass('slick-active').attr('aria-hidden', 'false');
                }
            }

        }

        if (_.options.lazyLoad === 'ondemand') {
            _.lazyLoad();
        }

    };

    Slick.prototype.setupInfinite = function() {

        var _ = this,
            i, slideIndex, infiniteCount;

        if (_.options.fade === true) {
            _.options.centerMode = false;
        }

        if (_.options.infinite === true && _.options.fade === false) {

            slideIndex = null;

            if (_.slideCount > _.options.slidesToShow) {

                if (_.options.centerMode === true) {
                    infiniteCount = _.options.slidesToShow + 1;
                } else {
                    infiniteCount = _.options.slidesToShow;
                }

                for (i = _.slideCount; i > (_.slideCount -
                        infiniteCount); i -= 1) {
                    slideIndex = i - 1;
                    $(_.$slides[slideIndex]).clone(true).attr('id', '')
                        .attr('data-slick-index', slideIndex - _.slideCount)
                        .prependTo(_.$slideTrack).addClass('slick-cloned');
                }
                for (i = 0; i < infiniteCount; i += 1) {
                    slideIndex = i;
                    $(_.$slides[slideIndex]).clone(true).attr('id', '')
                        .attr('data-slick-index', slideIndex + _.slideCount)
                        .appendTo(_.$slideTrack).addClass('slick-cloned');
                }
                _.$slideTrack.find('.slick-cloned').find('[id]').each(function() {
                    $(this).attr('id', '');
                });

            }

        }

    };

    Slick.prototype.setPaused = function(paused) {

        var _ = this;

        if (_.options.autoplay === true && _.options.pauseOnHover === true) {
            _.paused = paused;
            _.autoPlayClear();
        }
    };

    Slick.prototype.selectHandler = function(event) {

        var _ = this;

        var targetElement = $(event.target).is('.slick-slide') ?
            $(event.target) :
            $(event.target).parents('.slick-slide');

        var index = parseInt(targetElement.attr('data-slick-index'));
        alert(index)

        if (!index) index = 0;

        if (_.slideCount <= _.options.slidesToShow) {
            _.$slider.find('.slick-slide').removeClass('slick-active').attr('aria-hidden', 'true');
            _.$slides.eq(index).addClass('slick-active').attr("aria-hidden", "false");
            if (_.options.centerMode === true) {
                _.$slider.find('.slick-slide').removeClass('slick-center');
                _.$slides.eq(index).addClass('slick-center');
            }
            _.asNavFor(index);
            return;
        }
        _.slideHandler(index);

    };

    Slick.prototype.slideHandler = function(index, sync, dontAnimate) {

        var targetSlide, animSlide, oldSlide, slideLeft, targetLeft = null,
            _ = this;

        sync = sync || false;

        if (_.animating === true && _.options.waitForAnimate === true) {
            return;
        }

        if (_.options.fade === true && _.currentSlide === index) {
            return;
        }

        if (_.slideCount <= _.options.slidesToShow) {
            return;
        }

        if (sync === false) {
            _.asNavFor(index);
        }

        targetSlide = index;
        targetLeft = _.getLeft(targetSlide);
        slideLeft = _.getLeft(_.currentSlide);

        _.currentLeft = _.swipeLeft === null ? slideLeft : _.swipeLeft;

        if (_.options.infinite === false && _.options.centerMode === false && (index < 0 || index > _.getDotCount() * _.options.slidesToScroll)) {
            if (_.options.fade === false) {
                targetSlide = _.currentSlide;
                if (dontAnimate !== true) {
                    _.animateSlide(slideLeft, function() {
                        _.postSlide(targetSlide);
                    });
                } else {
                    _.postSlide(targetSlide);
                }
            }
            return;
        } else if (_.options.infinite === false && _.options.centerMode === true && (index < 0 || index > (_.slideCount - _.options.slidesToScroll))) {
            if (_.options.fade === false) {
                targetSlide = _.currentSlide;
                if (dontAnimate !== true) {
                    _.animateSlide(slideLeft, function() {
                        _.postSlide(targetSlide);
                    });
                } else {
                    _.postSlide(targetSlide);
                }
            }
            return;
        }

        if (_.options.autoplay === true) {
            clearInterval(_.autoPlayTimer);
        }

        if (targetSlide < 0) {
            if (_.slideCount % _.options.slidesToScroll !== 0) {
                animSlide = _.slideCount - (_.slideCount % _.options.slidesToScroll);
            } else {
                animSlide = _.slideCount + targetSlide;
            }
        } else if (targetSlide >= _.slideCount) {
            if (_.slideCount % _.options.slidesToScroll !== 0) {
                animSlide = 0;
            } else {
                animSlide = targetSlide - _.slideCount;
            }
        } else {
            animSlide = targetSlide;
        }

        _.animating = true;

        _.$slider.trigger("beforeChange", [_, _.currentSlide, animSlide]);

        oldSlide = _.currentSlide;
        _.currentSlide = animSlide;

        _.setSlideClasses(_.currentSlide);

        _.updateDots();
        _.updateArrows();

        if (_.options.fade === true) {
            if (dontAnimate !== true) {
                _.fadeSlide(animSlide, function() {
                    _.postSlide(animSlide);
                });
            } else {
                _.postSlide(animSlide);
            }
            _.animateHeight();
            return;
        }

        if (dontAnimate !== true) {
            _.animateSlide(targetLeft, function() {
                _.postSlide(animSlide);
            });
        } else {
            _.postSlide(animSlide);
        }

    };

    Slick.prototype.startLoad = function() {

        var _ = this;

        if (_.options.arrows === true && _.slideCount > _.options.slidesToShow) {

            _.$prevArrow.hide();
            _.$nextArrow.hide();

        }

        if (_.options.dots === true && _.slideCount > _.options.slidesToShow) {

            _.$dots.hide();

        }

        _.$slider.addClass('slick-loading');

    };

    Slick.prototype.swipeDirection = function() {

        var xDist, yDist, r, swipeAngle, _ = this;

        xDist = _.touchObject.startX - _.touchObject.curX;
        yDist = _.touchObject.startY - _.touchObject.curY;
        r = Math.atan2(yDist, xDist);

        swipeAngle = Math.round(r * 180 / Math.PI);
        if (swipeAngle < 0) {
            swipeAngle = 360 - Math.abs(swipeAngle);
        }

        if ((swipeAngle <= 45) && (swipeAngle >= 0)) {
            return (_.options.rtl === false ? 'left' : 'right');
        }
        if ((swipeAngle <= 360) && (swipeAngle >= 315)) {
            return (_.options.rtl === false ? 'left' : 'right');
        }
        if ((swipeAngle >= 135) && (swipeAngle <= 225)) {
            return (_.options.rtl === false ? 'right' : 'left');
        }
        if (_.options.verticalSwiping === true) {
            if ((swipeAngle >= 35) && (swipeAngle <= 135)) {
                return 'left';
            } else {
                return 'right';
            }
        }

        return 'vertical';

    };

    Slick.prototype.swipeEnd = function(event) {

        var _ = this,
            slideCount;

        _.dragging = false;

        _.shouldClick = (_.touchObject.swipeLength > 10) ? false : true;

        if (_.touchObject.curX === undefined) {
            return false;
        }

        if (_.touchObject.edgeHit === true) {
            _.$slider.trigger("edge", [_, _.swipeDirection()]);
        }

        if (_.touchObject.swipeLength >= _.touchObject.minSwipe) {

            switch (_.swipeDirection()) {
                case 'left':
                    slideCount = _.options.swipeToSlide ? _.checkNavigable(_.currentSlide + _.getSlideCount()) : _.currentSlide + _.getSlideCount();
                    _.slideHandler(slideCount);
                    _.currentDirection = 0;
                    _.touchObject = {};
                    _.$slider.trigger("swipe", [_, "left"]);
                    break;

                case 'right':
                    slideCount = _.options.swipeToSlide ? _.checkNavigable(_.currentSlide - _.getSlideCount()) : _.currentSlide - _.getSlideCount();
                    _.slideHandler(slideCount);
                    _.currentDirection = 1;
                    _.touchObject = {};
                    _.$slider.trigger("swipe", [_, "right"]);
                    break;
            }
        } else {
            if (_.touchObject.startX !== _.touchObject.curX) {
                _.slideHandler(_.currentSlide);
                _.touchObject = {};
            }
        }

    };

    Slick.prototype.swipeHandler = function(event) {

        var _ = this;

        if ((_.options.swipe === false) || ('ontouchend' in document && _.options.swipe === false)) {
            return;
        } else if (_.options.draggable === false && event.type.indexOf('mouse') !== -1) {
            return;
        }

        _.touchObject.fingerCount = event.originalEvent && event.originalEvent.touches !== undefined ?
            event.originalEvent.touches.length : 1;

        _.touchObject.minSwipe = _.listWidth / _.options
            .touchThreshold;

        if (_.options.verticalSwiping === true) {
            _.touchObject.minSwipe = _.listHeight / _.options
                .touchThreshold;
        }

        switch (event.data.action) {

            case 'start':
                _.swipeStart(event);
                break;

            case 'move':
                _.swipeMove(event);
                break;

            case 'end':
                _.swipeEnd(event);
                break;

        }

    };

    Slick.prototype.swipeMove = function(event) {

        var _ = this,
            edgeWasHit = false,
            curLeft, swipeDirection, swipeLength, positionOffset, touches;

        touches = event.originalEvent !== undefined ? event.originalEvent.touches : null;

        if (!_.dragging || touches && touches.length !== 1) {
            return false;
        }

        curLeft = _.getLeft(_.currentSlide);

        _.touchObject.curX = touches !== undefined ? touches[0].pageX : event.clientX;
        _.touchObject.curY = touches !== undefined ? touches[0].pageY : event.clientY;

        _.touchObject.swipeLength = Math.round(Math.sqrt(
            Math.pow(_.touchObject.curX - _.touchObject.startX, 2)));

        if (_.options.verticalSwiping === true) {
            _.touchObject.swipeLength = Math.round(Math.sqrt(
                Math.pow(_.touchObject.curY - _.touchObject.startY, 2)));
        }

        swipeDirection = _.swipeDirection();

        if (swipeDirection === 'vertical') {
            return;
        }

        if (event.originalEvent !== undefined && _.touchObject.swipeLength > 4) {
            event.preventDefault();
        }

        positionOffset = (_.options.rtl === false ? 1 : -1) * (_.touchObject.curX > _.touchObject.startX ? 1 : -1);
        if (_.options.verticalSwiping === true) {
            positionOffset = _.touchObject.curY > _.touchObject.startY ? 1 : -1;
        }


        swipeLength = _.touchObject.swipeLength;

        _.touchObject.edgeHit = false;

        if (_.options.infinite === false) {
            if ((_.currentSlide === 0 && swipeDirection === "right") || (_.currentSlide >= _.getDotCount() && swipeDirection === "left")) {
                swipeLength = _.touchObject.swipeLength * _.options.edgeFriction;
                _.touchObject.edgeHit = true;
            }
        }

        if (_.options.vertical === false) {
            _.swipeLeft = curLeft + swipeLength * positionOffset;
        } else {
            _.swipeLeft = curLeft + (swipeLength * (_.$list.height() / _.listWidth)) * positionOffset;
        }
        if (_.options.verticalSwiping === true) {
            _.swipeLeft = curLeft + swipeLength * positionOffset;
        }

        if (_.options.fade === true || _.options.touchMove === false) {
            return false;
        }

        if (_.animating === true) {
            _.swipeLeft = null;
            return false;
        }

        _.setCSS(_.swipeLeft);

    };

    Slick.prototype.swipeStart = function(event) {

        var _ = this,
            touches;

        if (_.touchObject.fingerCount !== 1 || _.slideCount <= _.options.slidesToShow) {
            _.touchObject = {};
            return false;
        }

        if (event.originalEvent !== undefined && event.originalEvent.touches !== undefined) {
            touches = event.originalEvent.touches[0];
        }

        _.touchObject.startX = _.touchObject.curX = touches !== undefined ? touches.pageX : event.clientX;
        _.touchObject.startY = _.touchObject.curY = touches !== undefined ? touches.pageY : event.clientY;

        _.dragging = true;

    };

    Slick.prototype.unfilterSlides = Slick.prototype.slickUnfilter = function() {

        var _ = this;

        if (_.$slidesCache !== null) {

            _.unload();

            _.$slideTrack.children(this.options.slide).detach();

            _.$slidesCache.appendTo(_.$slideTrack);

            _.reinit();

        }

    };

    Slick.prototype.unload = function() {

        var _ = this;

        $('.slick-cloned', _.$slider).remove();
        if (_.$dots) {
            _.$dots.remove();
        }
        if (_.$prevArrow && (typeof _.options.prevArrow !== 'object')) {
            _.$prevArrow.remove();
        }
        if (_.$nextArrow && (typeof _.options.nextArrow !== 'object')) {
            _.$nextArrow.remove();
        }
        _.$slides.removeClass('slick-slide slick-active slick-visible').attr("aria-hidden", "true").css('width', '');

    };

    Slick.prototype.unslick = function() {

        var _ = this;
        _.destroy();

    };

    Slick.prototype.updateArrows = function() {

        var _ = this,
            centerOffset;

        centerOffset = Math.floor(_.options.slidesToShow / 2);

        if (_.options.arrows === true && _.options.infinite !==
            true && _.slideCount > _.options.slidesToShow) {
            _.$prevArrow.removeClass('slick-disabled');
            _.$nextArrow.removeClass('slick-disabled');
            if (_.currentSlide === 0) {
                _.$prevArrow.addClass('slick-disabled');
                _.$nextArrow.removeClass('slick-disabled');
            } else if (_.currentSlide >= _.slideCount - _.options.slidesToShow && _.options.centerMode === false) {
                _.$nextArrow.addClass('slick-disabled');
                _.$prevArrow.removeClass('slick-disabled');
            } else if (_.currentSlide >= _.slideCount - 1 && _.options.centerMode === true) {
                _.$nextArrow.addClass('slick-disabled');
                _.$prevArrow.removeClass('slick-disabled');
            }
        }

    };

    Slick.prototype.updateDots = function() {

        var _ = this;

        if (_.$dots !== null) {

            _.$dots.find('li').removeClass('slick-active').attr("aria-hidden", "true");
            _.$dots.find('li').eq(Math.floor(_.currentSlide / _.options.slidesToScroll)).addClass('slick-active').attr("aria-hidden", "false");

        }

    };

    Slick.prototype.visibility = function() {

        var _ = this;

        if (document[_.hidden]) {
            _.paused = true;
            _.autoPlayClear();
        } else {
            _.paused = false;
            _.autoPlay();
        }

    };

    $.fn.slick = function() {
        var _ = this,
            opt = arguments[0],
            args = Array.prototype.slice.call(arguments, 1),
            l = _.length,
            i = 0,
            ret;
        for (i; i < l; i++) {
            if (typeof opt == 'object' || typeof opt == 'undefined')
                _[i].slick = new Slick(_[i], opt);
            else
                ret = _[i].slick[opt].apply(_[i].slick, args);
            if (typeof ret != 'undefined') return ret;
        }
        return _;
    };

}));
;
/*
 * jQuery Autocomplete plugin 1.1
 *
 * Copyright (c) 2009 Jörn Zaefferer
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 * Revision: $Id: jquery.autocompleteV1.js 15 2009-08-22 10:30:27Z joern.zaefferer $
 */

; (function ($) {
    // Above the Treeline Edit:
    //
    // accentMap contains a mapping of all the accent characters that a user can search by.
    //
    // This is a new addition to the libarary, not an alteration.
    var accentMap = {
        "á": "a",
        "Á": "a",
        "à": "a",
        "À": "a",
        "â": "a",
        "Â": "a",
        "ä": "a",
        "Ä": "a",
        "ã": "a",
        "Ã": "a",
        "å": "a",
        "Å": "a",
        "æ": "a",
        "Æ": "a",
        "ç": "c",
        "Ç": "c",
        "é": "e",
        "É": "e",
        "è": "e",
        "È": "e",
        "ê": "e",
        "Ê": "e",
        "ë": "e",
        "Ë": "e",
        "í": "i",
        "Í": "i",
        "ì": "i",
        "Ì": "i",
        "î": "i",
        "Î": "i",
        "ï": "i",
        "Ï": "i",
        "ñ": "n",
        "Ñ": "n",
        "ó": "o",
        "Ó": "o",
        "ò": "o",
        "Ò": "o",
        "ô": "o",
        "Ô": "o",
        "ö": "o",
        "Ö": "o",
        "õ": "o",
        "Õ": "o",
        "ø": "o",
        "Ø": "o",
        "œ": "o",
        "Œ": "o",
        "ß": "ss",
        "ú": "u",
        "Ú": "u",
        "ù": "u",
        "Ù": "u",
        "û": "u",
        "Û": "u",
        "ü": "u",
        "Ü": "u"
    };

    // Above the Treeline Edit:
    //
    // normalizeAccentCharacters takes a string and returns a new string with all of the accent characters
    // converted to their normalized forms (ex: ä -> a, ù -> u, ñ -> n, etc.)
    //
    // This is a new addition to the libarary, not an alteration.
    function normalizeAccentCharacters(str) {
        var ret = '';

        if (typeof str !== 'undefined') {
            for (var i = 0; i < str.length; i++) {
                var c = str.charAt(i);
                ret += accentMap[c] || c;
            }
        }

        return ret;
    };
	
$.fn.extend({
	autocomplete: function(urlOrData, options) {
		var isUrl = typeof urlOrData == "string";
		options = $.extend({}, $.Autocompleter.defaults, {
			url: isUrl ? urlOrData : null,
			data: isUrl ? null : urlOrData,
			delay: isUrl ? $.Autocompleter.defaults.delay : 10,
			max: options && !options.scroll ? 10 : 150
        }, options);
		
		// if highlight is set to false, replace it with a do-nothing function
		options.highlight = options.highlight || function(value) { return value; };
		
		// if the formatMatch option is not specified, then use formatItem for backwards compatibility
		options.formatMatch = options.formatMatch || options.formatItem;
		
		return this.each(function() {
			new $.Autocompleter(this, options);
		});
	},
	result: function(handler) {
		return this.bind("result", handler);
	},
	search: function(handler) {
		return this.trigger("search", [handler]);
	},
	flushCache: function() {
		return this.trigger("flushCache");
	},
	setOptions: function(options){
		return this.trigger("setOptions", [options]);
	},
	unautocomplete: function() {
		return this.trigger("unautocomplete");
	}
});

$.Autocompleter = function(input, options) {
    // Above the Treeline Edit:
    //
    // The variable options.data contains the searchable data the autocompleter was initialized with.
    // If this data is not a url, we need to store a normalized version of each string which will
    // remove the accent marks from each character. options.formatItem() returns the unnormalized version
    // of the data that the user is providing us, once it has been normalized, it will be stored
    // in entry.normalized.
    var isUrl = typeof options.data === "string";
    if (!isUrl && typeof options.data !== "undefined" && options.data !== null) {
        Object.keys(options.data).forEach(function(key) {
            var entry = options.data[key];

            entry.normalized = normalizeAccentCharacters(options.formatItem(entry));
        });
    }

	var KEY = {
		UP: 38,
		DOWN: 40,
		DEL: 46,
		TAB: 9,
		RETURN: 13,
		ESC: 27,
		COMMA: 188,
		PAGEUP: 33,
		PAGEDOWN: 34,
		BACKSPACE: 8
	};

	// Create $ object for input element
	var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);

	var timeout;
	var previousValue = "";
	var cache = $.Autocompleter.Cache(options);
	var hasFocus = 0;
	var lastKeyPressCode;
	var config = {
		mouseDownOnSelect: false
	};
	var select = $.Autocompleter.Select(options, input, selectCurrent, config);
	
	var blockSubmit;
	
	// prevent form submit in opera when selecting with return key
	$.browser.opera && $(input.form).bind("submit.autocomplete", function() {
		if (blockSubmit) {
			blockSubmit = false;
			return false;
		}
	});
	
	// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
	$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
		// a keypress means the input has focus
		// avoids issue where input had focus before the autocomplete was applied
		hasFocus = 1;
		// track last key pressed
		lastKeyPressCode = event.keyCode;
		switch(event.keyCode) {
		
			case KEY.UP:
				event.preventDefault();
				if ( select.visible() ) {
					select.prev();
				} else {
					onChange(0, true);
				}
				break;
				
			case KEY.DOWN:
				event.preventDefault();
				if ( select.visible() ) {
					select.next();
				} else {
					onChange(0, true);
				}
				break;
				
			case KEY.PAGEUP:
				event.preventDefault();
				if ( select.visible() ) {
					select.pageUp();
				} else {
					onChange(0, true);
				}
				break;
				
			case KEY.PAGEDOWN:
				event.preventDefault();
				if ( select.visible() ) {
					select.pageDown();
				} else {
					onChange(0, true);
				}
				break;
			
			// matches also semicolon
			case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
			case KEY.TAB:
			case KEY.RETURN:
				if( selectCurrent() ) {
					// stop default to prevent a form submit, Opera needs special handling
					event.preventDefault();
					blockSubmit = true;
					return false;
				}
				break;
				
			case KEY.ESC:
				select.hide();
				reset();
				break;
				
			default:
				clearTimeout(timeout);
				timeout = setTimeout(onChange, options.delay);
				break;
		}
	}).focus(function(){
		// track whether the field has focus, we shouldn't process any
		// results if the field no longer has focus
		hasFocus++;
	}).blur(function() {
		hasFocus = 0;
		if (!config.mouseDownOnSelect) {
			hideResults();
			
		}
	}).click(function() {
		// show select when clicking in a focused field
		if ( hasFocus++ > 1 && !select.visible() ) {
			onChange(0, true);
		}
	}).bind("search", function() {
		// TODO why not just specifying both arguments?
		var fn = (arguments.length > 1) ? arguments[1] : null;
		function findValueCallback(q, data) {
			var result;
			if( data && data.length ) {
				for (var i=0; i < data.length; i++) {
					if( data[i].result.toLowerCase() == q.toLowerCase() ) {
						result = data[i];
						break;
					}
				}
			}
			if( typeof fn == "function" ) fn(result);
			else $input.trigger("result", result && [result.data, result.value]);
		}
		$.each(trimWords($input.val()), function(i, value) {
			request(value, findValueCallback, findValueCallback);
		});
	}).bind("flushCache", function() {
		cache.flush();
	}).bind("setOptions", function() {
		$.extend(options, arguments[1]);
		// if we've updated the data, repopulate
		if ( "data" in arguments[1] )
			cache.populate();
	}).bind("unautocomplete", function() {
		select.unbind();
		$input.unbind();
		$(input.form).unbind(".autocomplete");
    });


	function selectCurrent() {
		var selected = select.selected();
		if( !selected )
			return false;
		
		var v = selected.result;
		previousValue = v;
		
		if ( options.multiple ) {
			var words = trimWords($input.val());
			if ( words.length > 1 ) {
				var seperator = options.multipleSeparator.length;
				var cursorAt = $(input).selection().start;
				var wordAt, progress = 0;
				$.each(words, function(i, word) {
					progress += word.length;
					if (cursorAt <= progress) {
						wordAt = i;
						return false;
					}
					progress += seperator;
				});
				words[wordAt] = v;
				// TODO this should set the cursor to the right position, but it gets overriden somewhere
				//$.Autocompleter.Selection(input, progress + seperator, progress + seperator);
				v = words.join( options.multipleSeparator );
			}
			v += options.multipleSeparator;
		}
		
		$input.val(v);
		hideResultsNow();
		$input.trigger("result", [selected.data, selected.value]);
		return true;
	}
	function reset(){
	    if (options.reset) options.reset();
	}
	function onChange(crap, skipPrevCheck) {
		if( lastKeyPressCode == KEY.DEL ) {
			select.hide();
			reset();
			return;
		}

        // Above the Treeline Edit:
        //
        // The variable currentValue stores the term that is being typed by the user.
        // To allow users to search for items that contain accent marks (ex: á, í, ñ, etc.) we will be normalizing
        // all searchable strings to remove the accent marks from them. Because of this, if the user actually does
        // type a character with an accent mark, to ensure that a match is found, we also need to normalize the query.
        // 
        // original version:
        // 
        // var currentValue = $input.val();
        var currentValue = normalizeAccentCharacters($input.val());
		
		if ( !skipPrevCheck && currentValue == previousValue )
			return;
		
		previousValue = currentValue;
		
		currentValue = lastWord(currentValue);
		if ( currentValue.length >= options.minChars) {
			$input.addClass(options.loadingClass);
			if (!options.matchCase)
				currentValue = currentValue.toLowerCase();
			request(currentValue, receiveData, hideResultsNow);
		} else {
			stopLoading();
			select.hide();
			reset();
		}
	};
	
	function trimWords(value) {
		if (!value)
			return [""];
		if (!options.multiple)
			return [$.trim(value)];
		return $.map(value.split(options.multipleSeparator), function(word) {
			return $.trim(value).length ? $.trim(word) : null;
		});
	}
	
	function lastWord(value) {
		if ( !options.multiple )
			return value;
		var words = trimWords(value);
		if (words.length == 1) 
			return words[0];
		var cursorAt = $(input).selection().start;
		if (cursorAt == value.length) {
			words = trimWords(value)
		} else {
			words = trimWords(value.replace(value.substring(cursorAt), ""));
		}
		return words[words.length - 1];
	}
	
	// fills in the input box w/the first match (assumed to be the best match)
	// q: the term entered
	// sValue: the first matching result
	function autoFill(q, sValue){
		// autofill in the complete box w/the first match as long as the user hasn't entered in more data
		// if the last user key pressed was backspace, don't autofill
		if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
			// fill in the value (keep the case the user has typed)
			$input.val($input.val() + sValue.substring(lastWord(previousValue).length));
			// select the portion of the value not typed by the user (so the next character will erase)
			$(input).selection(previousValue.length, previousValue.length + sValue.length);
		}
	};

	function hideResults() {
		clearTimeout(timeout);
		timeout = setTimeout(hideResultsNow, 200);
	};

	function hideResultsNow() {
		var wasVisible = select.visible();
		select.hide();
		clearTimeout(timeout);
		stopLoading();
		if (options.mustMatch) {
			// call search and run callback
			$input.search(
				function (result){
					// if no value found, clear the input box
					if( !result ) {
						if (options.multiple) {
							var words = trimWords($input.val()).slice(0, -1);
							$input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
						}
						else {
							$input.val( "" );
						}
					}
				}
			);
		}
	};

	function receiveData(q, data) {
		if ( data && data.length && hasFocus ) {
			stopLoading();
			select.display(data, q);
			autoFill(q, data[0].value);
			select.show();
		} else {
			hideResultsNow();
		}
	};

	function request(term, success, failure) {
		if (!options.matchCase)
            term = term.toLowerCase();
        var data = cache.load(term);
		// recieve the cached data
		if (data && data.length) {
			success(term, data);
		// if an AJAX url has been supplied, try loading the data now
		} else if( (typeof options.url == "string") && (options.url.length > 0) ){
			
			var extraParams = {
				timestamp: +new Date()
			};
			$.each(options.extraParams, function(key, param) {
				extraParams[key] = typeof param == "function" ? param() : param;
			});
			
			$.ajax({
				// try to leverage ajaxQueue plugin to abort previous requests
				mode: "abort",
				// limit abortion to this input
				port: "autocomplete" + input.name,
				dataType: options.dataType,
				url: options.url,
				data: $.extend({
					q: lastWord(term),
					limit: options.max
				}, extraParams),
				success: function(data) {
					var parsed = options.parse && options.parse(data) || parse(data);
					cache.add(term, parsed);
					success(term, parsed);
				}
			});
		} else {
			// if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
			select.emptyList();
			failure(term);
		}
	};
	
	function parse(data) {
		var parsed = [];
		var rows = data.split("\n");
		for (var i=0; i < rows.length; i++) {
			var row = $.trim(rows[i]);
			if (row) {
				row = row.split("|");
				parsed[parsed.length] = {
					data: row,
					value: row[0],
					result: options.formatResult && options.formatResult(row, row[0]) || row[0]
				};
			}
		}
		return parsed;
	};

	function stopLoading() {
		$input.removeClass(options.loadingClass);
	};

};

$.Autocompleter.defaults = {
	inputClass: "ac_input",
	resultsClass: "ac_results",
	loadingClass: "ac_loading",
	minChars: 1,
	delay: 400,
	matchCase: false,
	matchSubset: true,
	matchContains: false,
	cacheLength: 10,
	max: 100,
	mustMatch: false,
	extraParams: {},
	selectFirst: true,
	formatItem: function(row) { return row[0]; },
	formatMatch: null,
	postProcess: null,
	reset: null,
	autoFill: false,
	width: 0,
	multiple: false,
	multipleSeparator: ", ",
    highlight: function (value, term, normalized) {
        // Above the Treeline Edit:
        //
        // highlight takes 3 parameters:
        //      1. value: the version of the data that will be presented to the user
        //      2. term: the normalized search term entered by the user
        //      3. normalized: the normalized version of value
        // 
        // The way highlight works is by inserting a <strong></strong> tag around the matched word, so for example:
        //
        // value = "this is my string that I will be searching for."
        // term = "will"
        // result = "this is my string that I <strong>will</string> be searching for."
        // 
        // This works fine for normalized strings, but for any string that contains an accent mark, the matched word(s) will not
        // be highlighted since we normalize the search term, for example:
        //
        // value = "thís ís my stríng that í wíll be searching for." (notice the use of í instead of i)
        // term = "will" (original search term could have been wíll but it was normalized)
        // result = "thís ís my stríng that í wíll be searching for."
        //
        // So, to work around this, we actually do the searching on the normalized string instead, then we
        // manually insert <strong> and </strong> tags into the value string based on the positions of
        // <strong> and </strong> in the normalized string after searching. For example:
        //
        // value = "thís ís my stríng that í wíll be searching for."
        // normalized = "this is my string that I will be searching for."
        // term = "will"
        // replaced = "this is my string that I <strong>will</string> be searching for."
        // value = "thís ís my stríng that í <strong>wíll</strong> be searching for."
        //
        // original version of this function: 
        //
        // highlight: function(value, term) {
        //     return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
        // }

        if (typeof normalized === undefined || normalized === null || normalized === '') {
            normalized = value;
        }

        var replaced = normalized.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
        var start = replaced.indexOf("<strong>");
        var end = replaced.indexOf("</strong>");

        value = value.slice(0, start) + "<strong>" + value.slice(start);
        value = value.slice(0, end) + "</strong>" + value.slice(end);

		return value;
    },
    scroll: true,
    scrollHeight: 180
};

$.Autocompleter.Cache = function(options) {

	var data = {};
    var length = 0;
    
    function matchSubset(s, sub) {
		if (!options.matchCase) 
			s = s.toLowerCase();
        var i = s.indexOf(sub);
		if (options.matchContains == "word"){
            i = s.toLowerCase().search("\\b" + sub.toLowerCase());
		}
		if (i == -1) return false;
		return i == 0 || options.matchContains;
	};
	
	function add(q, value) {
		if (length > options.cacheLength){
			flush();
		}
		if (!data[q]){ 
			length++;
        }
		data[q] = value;
	}
	
	function populate(){
		if( !options.data ) return false;
		// track the matches
		var stMatchSets = {},
			nullData = 0;

		// no url was specified, we need to adjust the cache length to make sure it fits the local data store
		if( !options.url ) options.cacheLength = 1;
		
		// track all options for minChars = 0
		stMatchSets[""] = [];
		
		// loop through the array and create a lookup structure
		for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
			var rawValue = options.data[i];
			// if rawValue is a string, make an array otherwise just reference the array
            rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
			
			var value = options.formatMatch(rawValue, i+1, options.data.length);
			if ( value === false )
				continue;
				
			var firstChar = value.charAt(0).toLowerCase();
			// if no lookup array for this character exists, look it up now
			if( !stMatchSets[firstChar] ) 
				stMatchSets[firstChar] = [];

			// if the match is a string
			var row = {
				value: value,
				data: rawValue,
				result: options.formatResult && options.formatResult(rawValue) || value
			};
			
			// push the current match into the set list
			stMatchSets[firstChar].push(row);

			// keep track of minChars zero items
			if ( nullData++ < options.max ) {
				stMatchSets[""].push(row);
			}
		};

		// add the data items to the cache
		$.each(stMatchSets, function(i, value) {
			// increase the cache size
			options.cacheLength++;
			// add to the cache
			add(i, value);
		});
	}
	
	// populate any existing data
	setTimeout(populate, 25);
	
	function flush(){
		data = {};
		length = 0;
	}
	
	return {
		flush: flush,
		add: add,
		populate: populate,
		load: function(q) {
			if (!options.cacheLength || !length)
                return null;

            if (!options.url && options.customMatch) {
                return options.customMatch(q, data);
            }
			/* 
			 * if dealing w/local data and matchContains than we must make sure
			 * to loop through all the data collections looking for matches
			 */
			if( !options.url && options.matchContains ){
				// track all matches
				var csub = [];
				// loop through all the data grids for matches
				for( var k in data ){
					// don't search through the stMatchSets[""] (minChars: 0) cache
					// this prevents duplicates
					if( k.length > 0 ){
                        var c = data[k];
						$.each(c, function(i, x) {
							// if we've got a match, add it to the array
							if (matchSubset(x.value, q)) {
								csub.push(x);
							}
						});
					}
				}				
				return csub;
			} else 
			// if the exact item exists, use it
			if (data[q]){
				return data[q];
			} else
			if (options.matchSubset) {
				for (var i = q.length - 1; i >= options.minChars; i--) {
					var c = data[q.substr(0, i)];
					if (c) {
						var csub = [];
						$.each(c, function(i, x) {
							if (matchSubset(x.value, q)) {
								csub[csub.length] = x;
							}
						});
						return csub;
					}
				}
			}
			return null;
		}
	};
};

$.Autocompleter.Select = function (options, input, select, config) {
	var CLASSES = {
		ACTIVE: "ac_over"
	};
	
	var listItems,
		active = -1,
		data,
		term = "",
		needsInit = true,
		element,
		list;
	
	// Create results
	function init() {
		if (!needsInit)
			return;
		element = $("<div/>")
		.hide()
		.addClass(options.resultsClass)
		.css("position", "absolute")
		.appendTo(document.body);
	
		list = $("<ul/>").appendTo(element).mouseover( function(event) {
			if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
	            active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
			    $(target(event)).addClass(CLASSES.ACTIVE);            
	        }
		}).click(function(event) {
			$(target(event)).addClass(CLASSES.ACTIVE);
			select();
			// TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
			input.focus();
			return false;
		}).mousedown(function() {
			config.mouseDownOnSelect = true;
		}).mouseup(function() {
			config.mouseDownOnSelect = false;
		});
		
		if( options.width > 0 )
			element.css("width", options.width);
			
		needsInit = false;
	} 
	
	function target(event) {
		var element = event.target;
		while(element && element.tagName != "LI")
			element = element.parentNode;
		// more fun with IE, sometimes event.target is empty, just ignore it then
		if(!element)
			return [];
		return element;
	}

	function moveSelect(step) {
		listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
		movePosition(step);
        var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
        if(options.scroll) {
            var offset = 0;
            listItems.slice(0, active).each(function() {
				offset += this.offsetHeight;
			});
            if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
                list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
            } else if(offset < list.scrollTop()) {
                list.scrollTop(offset);
            }
        }
	};
	
	function movePosition(step) {
		active += step;
		if (active < 0) {
			active = listItems.size() - 1;
		} else if (active >= listItems.size()) {
			active = 0;
		}
	}
	
    function limitNumberOfItems(available) {
		return options.max && options.max < available
			? options.max
			: available;
	}
    function fillList() {
		list.empty();
		var max = limitNumberOfItems(data.length);
		for (var i=0; i < max; i++) {
			if (!data[i])
                continue;
            var formatted = options.formatItem(data[i].data, i + 1, max, data[i].value, term);
            var normalized = options.formatMatch(data[i].data, i + 1, max, data[i].value, term);
			if ( formatted === false )
                continue;
            // Above the Treeline Edit:
            //
            // To allow the highlight function to work with normalized strings, we need to pass the normalized string
            //
            // original version:
            //
            // var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
            var li = $("<li/>").html( options.highlight(formatted, term, normalized) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
			$.data(li, "ac_data", data[i]);
		}
		listItems = list.find("li");
		if ( options.selectFirst ) {
			listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
			active = 0;
		}
		// apply bgiframe if available
		if ( $.fn.bgiframe )
			list.bgiframe();
	}
	
	return {
		display: function(d, q) {
			init();
			data = d;
			term = q;
			fillList();
			if (options.postProcess) options.postProcess(data);
		},
		next: function() {
			moveSelect(1);
		},
		prev: function() {
			moveSelect(-1);
		},
		pageUp: function() {
			if (active != 0 && active - 8 < 0) {
				moveSelect( -active );
			} else {
				moveSelect(-8);
			}
		},
		pageDown: function() {
			if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
				moveSelect( listItems.size() - 1 - active );
			} else {
				moveSelect(8);
			}
		},
		hide: function() {
			element && element.hide();
			listItems && listItems.removeClass(CLASSES.ACTIVE);
			active = -1;
		},
		visible : function() {
			return element && element.is(":visible");
		},
		current: function() {
			return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
		},
		show: function() {
			var offset = $(input).offset();
			element.css({
				width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
				top: offset.top + input.offsetHeight,
				left: offset.left
			}).show();
            if(options.scroll) {
                list.scrollTop(0);
                list.css({
					maxHeight: options.scrollHeight,
					overflow: 'auto'
				});
				
                if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
					var listHeight = 0;
					listItems.each(function() {
						listHeight += this.offsetHeight;
					});
					var scrollbarsVisible = listHeight > options.scrollHeight;
                    list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
					if (!scrollbarsVisible) {
						// IE doesn't recalculate width when scrollbar disappears
						listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
					}
                }
                
            }
		},
		selected: function() {
			var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
			return selected && selected.length && $.data(selected[0], "ac_data");
		},
		emptyList: function (){
			list && list.empty();
		},
		unbind: function() {
			element && element.remove();
		}
	};
};

$.fn.selection = function(start, end) {
	if (start !== undefined) {
		return this.each(function() {
			if( this.createTextRange ){
				var selRange = this.createTextRange();
				if (end === undefined || start == end) {
					selRange.move("character", start);
					selRange.select();
				} else {
					selRange.collapse(true);
					selRange.moveStart("character", start);
					selRange.moveEnd("character", end);
					selRange.select();
				}
			} else if( this.setSelectionRange ){
				this.setSelectionRange(start, end);
			} else if( this.selectionStart ){
				this.selectionStart = start;
				this.selectionEnd = end;
			}
		});
	}
	var field = this[0];
	if ( field.createTextRange ) {
		var range = document.selection.createRange(),
			orig = field.value,
			teststring = "<->",
			textLength = range.text.length;
		range.text = teststring;
		var caretAt = field.value.indexOf(teststring);
		field.value = orig;
		this.selection(caretAt, caretAt + textLength);
		return {
			start: caretAt,
			end: caretAt + textLength
		}
	} else if( field.selectionStart !== undefined ){
		return {
			start: field.selectionStart,
			end: field.selectionEnd
		}
	}
};

})(jQuery);;
/*
 * jQuery appear plugin
 *
 * Copyright (c) 2012 Andrey Sidorov
 * licensed under MIT license.
 *
 * https://github.com/morr/jquery.appear/
 *
 * Version: 0.3.4
 */
(function($) {
  var selectors = [];

  var check_binded = false;
  var check_lock = false;
  var defaults = {
    interval: 250,
    force_process: false
  }
  var $window = $(window);

  var $prior_appeared;

  function process() {
    check_lock = false;
    for (var index = 0, selectorsLength = selectors.length; index < selectorsLength; index++) {
      var $appeared = $(selectors[index]).filter(function() {
        return $(this).is(':appeared');
      });

      $appeared.trigger('appear', [$appeared]);

      if ($prior_appeared) {
        var $disappeared = $prior_appeared.not($appeared);
        $disappeared.trigger('disappear', [$disappeared]);
      }
      $prior_appeared = $appeared;
    }
  }

  // "appeared" custom filter
  $.expr[':']['appeared'] = function(element) {
    var $element = $(element);
    if (!$element.is(':visible')) {
      return false;
    }

    var window_left = $window.scrollLeft();
    var window_top = $window.scrollTop();
    var offset = $element.offset();
    var left = offset.left;
    var top = offset.top;

    if (top + $element.height() >= window_top &&
        top - ($element.data('appear-top-offset') || 0) <= window_top + $window.height() &&
        left + $element.width() >= window_left &&
        left - ($element.data('appear-left-offset') || 0) <= window_left + $window.width()) {
      return true;
    } else {
      return false;
    }
  }

  $.fn.extend({
    // watching for element's appearance in browser viewport
    appear: function(options) {
      var opts = $.extend({}, defaults, options || {});
      var selector = this.selector || this;
      if (!check_binded) {
        var on_check = function() {
          if (check_lock) {
            return;
          }
          check_lock = true;

          setTimeout(process, opts.interval);
        };

        $(window).scroll(on_check).resize(on_check);
        check_binded = true;
      }

      if (opts.force_process) {
        setTimeout(process, opts.interval);
      }
      selectors.push(selector);
      return $(selector);
    }
  });

  $.extend({
    // force elements's appearance check
    force_appear: function() {
      if (check_binded) {
        process();
        return true;
      };
      return false;
    }
  });
})(jQuery);;
/*!
 * jQuery Raty - A Star Rating Plugin
 *
 * The MIT License
 *
 * @author  : Washington Botelho
 * @doc     : http://wbotelhos.com/raty
 * @version : 2.7.0
 *
 */

;
(function($) {
  'use strict';

  var methods = {
    init: function(options) {
      return this.each(function() {
        this.self = $(this);

        methods.destroy.call(this.self);

        this.opt = $.extend(true, {}, $.fn.raty.defaults, options);

        methods._adjustCallback.call(this);
        methods._adjustNumber.call(this);
        methods._adjustHints.call(this);

        this.opt.score = methods._adjustedScore.call(this, this.opt.score);

        if (this.opt.starType !== 'img') {
          methods._adjustStarType.call(this);
        }

        methods._adjustPath.call(this);
        methods._createStars.call(this);

        if (this.opt.cancel) {
          methods._createCancel.call(this);
        }

        if (this.opt.precision) {
          methods._adjustPrecision.call(this);
        }

        methods._createScore.call(this);
        methods._apply.call(this, this.opt.score);
        methods._setTitle.call(this, this.opt.score);
        methods._target.call(this, this.opt.score);

        if (this.opt.readOnly) {
          methods._lock.call(this);
        } else {
          this.style.cursor = 'pointer';

          methods._binds.call(this);
        }
      });
    },

    _adjustCallback: function() {
      var options = ['number', 'readOnly', 'score', 'scoreName', 'target'];

      for (var i = 0; i < options.length; i++) {
        if (typeof this.opt[options[i]] === 'function') {
          this.opt[options[i]] = this.opt[options[i]].call(this);
        }
      }
    },

    _adjustedScore: function(score) {
      if (!score) {
        return score;
      }

      return methods._between(score, 0, this.opt.number);
    },

    _adjustHints: function() {
      if (!this.opt.hints) {
        this.opt.hints = [];
      }

      if (!this.opt.halfShow && !this.opt.half) {
        return;
      }

      var steps = this.opt.precision ? 10 : 2;

      for (var i = 0; i < this.opt.number; i++) {
        var group = this.opt.hints[i];

        if (Object.prototype.toString.call(group) !== '[object Array]') {
          group = [group];
        }

        this.opt.hints[i] = [];

        for (var j = 0; j < steps; j++) {
          var
            hint = group[j],
            last = group[group.length - 1];

          if (last === undefined) {
            last = null;
          }

          this.opt.hints[i][j] = hint === undefined ? last : hint;
        }
      }
    },

    _adjustNumber: function() {
      this.opt.number = methods._between(this.opt.number, 1, this.opt.numberMax);
    },

    _adjustPath: function() {
      this.opt.path = this.opt.path || '';

      if (this.opt.path && this.opt.path.charAt(this.opt.path.length - 1) !== '/') {
        this.opt.path += '/';
      }
    },

    _adjustPrecision: function() {
      this.opt.half = true;
    },

    _adjustStarType: function() {
      var replaces = ['cancelOff', 'cancelOn', 'starHalf', 'starOff', 'starOn'];

      this.opt.path = '';

      for (var i = 0; i < replaces.length; i++) {
        this.opt[replaces[i]] = this.opt[replaces[i]].replace('.', '-');
      }
    },

    _apply: function(score) {
      methods._fill.call(this, score);

      if (score) {
        if (score > 0) {
          this.score.val(score);
        }

        methods._roundStars.call(this, score);
      }
    },

    _between: function(value, min, max) {
      return Math.min(Math.max(parseFloat(value), min), max);
    },

    _binds: function() {
      if (this.cancel) {
        methods._bindOverCancel.call(this);
        methods._bindClickCancel.call(this);
        methods._bindOutCancel.call(this);
      }

      methods._bindOver.call(this);
      methods._bindClick.call(this);
      methods._bindOut.call(this);
    },

    _bindClick: function() {
      var that = this;

      that.stars.on('click.raty', function(evt) {
        var
          execute = true,
          score   = (that.opt.half || that.opt.precision) ? that.self.data('score') : (this.alt || $(this).data('alt'));

        if (that.opt.click) {
          execute = that.opt.click.call(that, +score, evt);
        }

        if (execute || execute === undefined) {
          if (that.opt.half && !that.opt.precision) {
            score = methods._roundHalfScore.call(that, score);
          }

          methods._apply.call(that, score);
        }
      });
    },

    _bindClickCancel: function() {
      var that = this;

      that.cancel.on('click.raty', function(evt) {
        that.score.removeAttr('value');

        if (that.opt.click) {
          that.opt.click.call(that, null, evt);
        }
      });
    },

    _bindOut: function() {
      var that = this;

      that.self.on('mouseleave.raty', function(evt) {
        var score = +that.score.val() || undefined;

        methods._apply.call(that, score);
        methods._target.call(that, score, evt);
        methods._resetTitle.call(that);

        if (that.opt.mouseout) {
          that.opt.mouseout.call(that, score, evt);
        }
      });
    },

    _bindOutCancel: function() {
      var that = this;

      that.cancel.on('mouseleave.raty', function(evt) {
        var icon = that.opt.cancelOff;

        if (that.opt.starType !== 'img') {
          icon = that.opt.cancelClass + ' ' + icon;
        }

        methods._setIcon.call(that, this, icon);

        if (that.opt.mouseout) {
          var score = +that.score.val() || undefined;

          that.opt.mouseout.call(that, score, evt);
        }
      });
    },

    _bindOver: function() {
      var that   = this,
          action = that.opt.half ? 'mousemove.raty' : 'mouseover.raty';

      that.stars.on(action, function(evt) {
        var score = methods._getScoreByPosition.call(that, evt, this);

        methods._fill.call(that, score);

        if (that.opt.half) {
          methods._roundStars.call(that, score, evt);
          methods._setTitle.call(that, score, evt);

          that.self.data('score', score);
        }

        methods._target.call(that, score, evt);

        if (that.opt.mouseover) {
          that.opt.mouseover.call(that, score, evt);
        }
      });
    },

    _bindOverCancel: function() {
      var that = this;

      that.cancel.on('mouseover.raty', function(evt) {
        var
          starOff = that.opt.path + that.opt.starOff,
          icon    = that.opt.cancelOn;

        if (that.opt.starType === 'img') {
          that.stars.attr('src', starOff);
        } else {
          icon = that.opt.cancelClass + ' ' + icon;

          that.stars.attr('class', starOff);
        }

        methods._setIcon.call(that, this, icon);
        methods._target.call(that, null, evt);

        if (that.opt.mouseover) {
          that.opt.mouseover.call(that, null);
        }
      });
    },

    _buildScoreField: function() {
      return $('<input />', { name: this.opt.scoreName, type: 'hidden' }).appendTo(this);
    },

    _createCancel: function() {
      var icon   = this.opt.path + this.opt.cancelOff,
          cancel = $('<' + this.opt.starType + ' />', { title: this.opt.cancelHint, 'class': this.opt.cancelClass });

      if (this.opt.starType === 'img') {
        cancel.attr({ src: icon, alt: 'x' });
      } else {
        // TODO: use $.data
        cancel.attr('data-alt', 'x').addClass(icon);
      }

      if (this.opt.cancelPlace === 'left') {
        this.self.prepend('&#160;').prepend(cancel);
      } else {
        this.self.append('&#160;').append(cancel);
      }

      this.cancel = cancel;
    },

    _createScore: function() {
      var score = $(this.opt.targetScore);

      this.score = score.length ? score : methods._buildScoreField.call(this);
    },

    _createStars: function() {
      for (var i = 1; i <= this.opt.number; i++) {
        var
          name  = methods._nameForIndex.call(this, i),
          attrs = { alt: i, src: this.opt.path + this.opt[name] };

        if (this.opt.starType !== 'img') {
          attrs = { 'data-alt': i, 'class': attrs.src }; // TODO: use $.data.
        }

        attrs.title = methods._getHint.call(this, i);

        $('<' + this.opt.starType + ' />', attrs).appendTo(this);

        if (this.opt.space) {
          this.self.append(i < this.opt.number ? '&#160;' : '');
        }
      }

      this.stars = this.self.children(this.opt.starType);
    },

    _error: function(message) {
      $(this).text(message);

      $.error(message);
    },

    _fill: function(score) {
      var hash = 0;

      for (var i = 1; i <= this.stars.length; i++) {
        var
          icon,
          star   = this.stars[i - 1],
          turnOn = methods._turnOn.call(this, i, score);

        if (this.opt.iconRange && this.opt.iconRange.length > hash) {
          var irange = this.opt.iconRange[hash];

          icon = methods._getRangeIcon.call(this, irange, turnOn);

          if (i <= irange.range) {
            methods._setIcon.call(this, star, icon);
          }

          if (i === irange.range) {
            hash++;
          }
        } else {
          icon = this.opt[turnOn ? 'starOn' : 'starOff'];

          methods._setIcon.call(this, star, icon);
        }
      }
    },

    _getFirstDecimal: function(number) {
      var
        decimal = number.toString().split('.')[1],
        result  = 0;

      if (decimal) {
        result = parseInt(decimal.charAt(0), 10);

        if (decimal.slice(1, 5) === '9999') {
          result++;
        }
      }

      return result;
    },

    _getRangeIcon: function(irange, turnOn) {
      return turnOn ? irange.on || this.opt.starOn : irange.off || this.opt.starOff;
    },

    _getScoreByPosition: function(evt, icon) {
      var score = parseInt(icon.alt || icon.getAttribute('data-alt'), 10);

      if (this.opt.half) {
        var
          size    = methods._getWidth.call(this),
          percent = parseFloat((evt.pageX - $(icon).offset().left) / size);

        score = score - 1 + percent;
      }

      return score;
    },

    _getHint: function(score, evt) {
      if (score !== 0 && !score) {
        return this.opt.noRatedMsg;
      }

      var
        decimal = methods._getFirstDecimal.call(this, score),
        integer = Math.ceil(score),
        group   = this.opt.hints[(integer || 1) - 1],
        hint    = group,
        set     = !evt || this.move;

      if (this.opt.precision) {
        if (set) {
          decimal = decimal === 0 ? 9 : decimal - 1;
        }

        hint = group[decimal];
      } else if (this.opt.halfShow || this.opt.half) {
        decimal = set && decimal === 0 ? 1 : decimal > 5 ? 1 : 0;

        hint = group[decimal];
      }

      return hint === '' ? '' : hint || score;
    },

    _getWidth: function() {
      var width = this.stars[0].width || parseFloat(this.stars.eq(0).css('font-size'));

      if (!width) {
        methods._error.call(this, 'Could not get the icon width!');
      }

      return width;
    },

    _lock: function() {
      var hint = methods._getHint.call(this, this.score.val());

      this.style.cursor = '';
      this.title        = hint;

      this.score.prop('readonly', true);
      this.stars.prop('title', hint);

      if (this.cancel) {
        this.cancel.hide();
      }

      this.self.data('readonly', true);
    },

    _nameForIndex: function(i) {
      return this.opt.score && this.opt.score >= i ? 'starOn' : 'starOff';
    },

    _resetTitle: function(star) {
      for (var i = 0; i < this.opt.number; i++) {
        this.stars[i].title = methods._getHint.call(this, i + 1);
      }
    },

     _roundHalfScore: function(score) {
      var integer = parseInt(score, 10),
          decimal = methods._getFirstDecimal.call(this, score);

      if (decimal !== 0) {
        decimal = decimal > 5 ? 1 : 0.5;
      }

      return integer + decimal;
    },

    _roundStars: function(score, evt) {
      var
        decimal = (score % 1).toFixed(2),
        name    ;

      if (evt || this.move) {
        name = decimal > 0.5 ? 'starOn' : 'starHalf';
      } else if (decimal > this.opt.round.down) {               // Up:   [x.76 .. x.99]
        name = 'starOn';

        if (this.opt.halfShow && decimal < this.opt.round.up) { // Half: [x.26 .. x.75]
          name = 'starHalf';
        } else if (decimal < this.opt.round.full) {             // Down: [x.00 .. x.5]
          name = 'starOff';
        }
      }

      if (name) {
        var
          icon = this.opt[name],
          star = this.stars[Math.ceil(score) - 1];

        methods._setIcon.call(this, star, icon);
      }                                                         // Full down: [x.00 .. x.25]
    },

    _setIcon: function(star, icon) {
      star[this.opt.starType === 'img' ? 'src' : 'className'] = this.opt.path + icon;
    },

    _setTarget: function(target, score) {
      if (score) {
        score = this.opt.targetFormat.toString().replace('{score}', score);
      }

      if (target.is(':input')) {
        target.val(score);
      } else {
        target.html(score);
      }
    },

    _setTitle: function(score, evt) {
      if (score) {
        var
          integer = parseInt(Math.ceil(score), 10),
          star    = this.stars[integer - 1];

        star.title = methods._getHint.call(this, score, evt);
      }
    },

    _target: function(score, evt) {
      if (this.opt.target) {
        var target = $(this.opt.target);

        if (!target.length) {
          methods._error.call(this, 'Target selector invalid or missing!');
        }

        var mouseover = evt && evt.type === 'mouseover';

        if (score === undefined) {
          score = this.opt.targetText;
        } else if (score === null) {
          score = mouseover ? this.opt.cancelHint : this.opt.targetText;
        } else {
          if (this.opt.targetType === 'hint') {
            score = methods._getHint.call(this, score, evt);
          } else if (this.opt.precision) {
            score = parseFloat(score).toFixed(1);
          }

          var mousemove = evt && evt.type === 'mousemove';

          if (!mouseover && !mousemove && !this.opt.targetKeep) {
            score = this.opt.targetText;
          }
        }

        methods._setTarget.call(this, target, score);
      }
    },

    _turnOn: function(i, score) {
      return this.opt.single ? (i === score) : (i <= score);
    },

    _unlock: function() {
      this.style.cursor = 'pointer';
      this.removeAttribute('title');

      this.score.removeAttr('readonly');

      this.self.data('readonly', false);

      for (var i = 0; i < this.opt.number; i++) {
        this.stars[i].title = methods._getHint.call(this, i + 1);
      }

      if (this.cancel) {
        this.cancel.css('display', '');
      }
    },

    cancel: function(click) {
      return this.each(function() {
        var self = $(this);

        if (self.data('readonly') !== true) {
          methods[click ? 'click' : 'score'].call(self, null);

          this.score.removeAttr('value');
        }
      });
    },

    click: function(score) {
      return this.each(function() {
        if ($(this).data('readonly') !== true) {
          score = methods._adjustedScore.call(this, score);

          methods._apply.call(this, score);

          if (this.opt.click) {
            this.opt.click.call(this, score, $.Event('click'));
          }

          methods._target.call(this, score);
        }
      });
    },

    destroy: function() {
      return this.each(function() {
        var self = $(this),
            raw  = self.data('raw');

        if (raw) {
          self.off('.raty').empty().css({ cursor: raw.style.cursor }).removeData('readonly');
        } else {
          self.data('raw', self.clone()[0]);
        }
      });
    },

    getScore: function() {
      var score = [],
          value ;

      this.each(function() {
        value = this.score.val();

        score.push(value ? +value : undefined);
      });

      return (score.length > 1) ? score : score[0];
    },

    move: function(score) {
      return this.each(function() {
        var
          integer  = parseInt(score, 10),
          decimal  = methods._getFirstDecimal.call(this, score);

        if (integer >= this.opt.number) {
          integer = this.opt.number - 1;
          decimal = 10;
        }

        var
          width   = methods._getWidth.call(this),
          steps   = width / 10,
          star    = $(this.stars[integer]),
          percent = star.offset().left + steps * decimal,
          evt     = $.Event('mousemove', { pageX: percent });

        this.move = true;

        star.trigger(evt);

        this.move = false;
      });
    },

    readOnly: function(readonly) {
      return this.each(function() {
        var self = $(this);

        if (self.data('readonly') !== readonly) {
          if (readonly) {
            self.off('.raty').children('img').off('.raty');

            methods._lock.call(this);
          } else {
            methods._binds.call(this);
            methods._unlock.call(this);
          }

          self.data('readonly', readonly);
        }
      });
    },

    reload: function() {
      return methods.set.call(this, {});
    },

    score: function() {
      var self = $(this);

      return arguments.length ? methods.setScore.apply(self, arguments) : methods.getScore.call(self);
    },

    set: function(options) {
      return this.each(function() {
        $(this).raty($.extend({}, this.opt, options));
      });
    },

    setScore: function(score) {
      return this.each(function() {
        if ($(this).data('readonly') !== true) {
          score = methods._adjustedScore.call(this, score);

          methods._apply.call(this, score);
          methods._target.call(this, score);
        }
      });
    }
  };

  $.fn.raty = function(method) {
    if (methods[method]) {
      return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
    } else if (typeof method === 'object' || !method) {
      return methods.init.apply(this, arguments);
    } else {
      $.error('Method ' + method + ' does not exist!');
    }
  };

  $.fn.raty.defaults = {
    cancel       : false,
    cancelClass  : 'raty-cancel',
    cancelHint   : 'Cancel this rating!',
    cancelOff    : 'cancel-off.png',
    cancelOn     : 'cancel-on.png',
    cancelPlace  : 'left',
    click        : undefined,
    half         : false,
    halfShow     : true,
    hints        : ['bad', 'poor', 'regular', 'good', 'gorgeous'],
    iconRange    : undefined,
    mouseout     : undefined,
    mouseover    : undefined,
    noRatedMsg   : 'Not rated yet!',
    number       : 5,
    numberMax    : 20,
    path         : undefined,
    precision    : false,
    readOnly     : false,
    round        : { down: 0.25, full: 0.6, up: 0.76 },
    score        : undefined,
    scoreName    : 'score',
    single       : false,
    space        : true,
    starHalf     : 'star-half.png',
    starOff      : 'star-off.png',
    starOn       : 'star-on.png',
    starType     : 'img',
    target       : undefined,
    targetFormat : '{score}',
    targetKeep   : false,
    targetScore  : undefined,
    targetText   : '',
    targetType   : 'hint'
  };

})(jQuery);
;
/*! Copyright (c) 2011 Piotr Rochala (http://rocha.la)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * Version: 1.3.0
 *
 */
(function(f){jQuery.fn.extend({slimScroll:function(h){var a=f.extend({width:"auto",height:"250px",size:"7px",color:"#000",position:"right",distance:"1px",start:"top",opacity:0.4,alwaysVisible:!1,disableFadeOut:!1,railVisible:!1,railColor:"#333",railOpacity:0.2,railDraggable:!0,railClass:"slimScrollRail",barClass:"slimScrollBar",wrapperClass:"slimScrollDiv",allowPageScroll:!1,wheelStep:20,touchScrollStep:200,borderRadius:"7px",railBorderRadius:"7px"},h);this.each(function(){function r(d){if(s){d=d||
window.event;var c=0;d.wheelDelta&&(c=-d.wheelDelta/120);d.detail&&(c=d.detail/3);f(d.target||d.srcTarget||d.srcElement).closest("."+a.wrapperClass).is(b.parent())&&m(c,!0);d.preventDefault&&!k&&d.preventDefault();k||(d.returnValue=!1)}}function m(d,f,h){k=!1;var e=d,g=b.outerHeight()-c.outerHeight();f&&(e=parseInt(c.css("top"))+d*parseInt(a.wheelStep)/100*c.outerHeight(),e=Math.min(Math.max(e,0),g),e=0<d?Math.ceil(e):Math.floor(e),c.css({top:e+"px"}));l=parseInt(c.css("top"))/(b.outerHeight()-c.outerHeight());
e=l*(b[0].scrollHeight-b.outerHeight());h&&(e=d,d=e/b[0].scrollHeight*b.outerHeight(),d=Math.min(Math.max(d,0),g),c.css({top:d+"px"}));b.scrollTop(e);b.trigger("slimscrolling",~~e);v();p()}function C(){window.addEventListener?(this.addEventListener("DOMMouseScroll",r,!1),this.addEventListener("mousewheel",r,!1),this.addEventListener("MozMousePixelScroll",r,!1)):document.attachEvent("onmousewheel",r)}function w(){u=Math.max(b.outerHeight()/b[0].scrollHeight*b.outerHeight(),D);c.css({height:u+"px"});
var a=u==b.outerHeight()?"none":"block";c.css({display:a})}function v(){w();clearTimeout(A);l==~~l?(k=a.allowPageScroll,B!=l&&b.trigger("slimscroll",0==~~l?"top":"bottom")):k=!1;B=l;u>=b.outerHeight()?k=!0:(c.stop(!0,!0).fadeIn("fast"),a.railVisible&&g.stop(!0,!0).fadeIn("fast"))}function p(){a.alwaysVisible||(A=setTimeout(function(){a.disableFadeOut&&s||(x||y)||(c.fadeOut("slow"),g.fadeOut("slow"))},1E3))}var s,x,y,A,z,u,l,B,D=30,k=!1,b=f(this);if(b.parent().hasClass(a.wrapperClass)){var n=b.scrollTop(),
c=b.parent().find("."+a.barClass),g=b.parent().find("."+a.railClass);w();if(f.isPlainObject(h)){if("height"in h&&"auto"==h.height){b.parent().css("height","auto");b.css("height","auto");var q=b.parent().parent().height();b.parent().css("height",q);b.css("height",q)}if("scrollTo"in h)n=parseInt(a.scrollTo);else if("scrollBy"in h)n+=parseInt(a.scrollBy);else if("destroy"in h){c.remove();g.remove();b.unwrap();return}m(n,!1,!0)}}else{a.height="auto"==a.height?b.parent().height():a.height;n=f("<div></div>").addClass(a.wrapperClass).css({position:"relative",
overflow:"hidden",width:a.width,height:a.height});b.css({overflow:"hidden",width:a.width,height:a.height});var g=f("<div></div>").addClass(a.railClass).css({width:a.size,height:"100%",position:"absolute",top:0,display:a.alwaysVisible&&a.railVisible?"block":"none","border-radius":a.railBorderRadius,background:a.railColor,opacity:a.railOpacity,zIndex:90}),c=f("<div></div>").addClass(a.barClass).css({background:a.color,width:a.size,position:"absolute",top:0,opacity:a.opacity,display:a.alwaysVisible?
"block":"none","border-radius":a.borderRadius,BorderRadius:a.borderRadius,MozBorderRadius:a.borderRadius,WebkitBorderRadius:a.borderRadius,zIndex:99}),q="right"==a.position?{right:a.distance}:{left:a.distance};g.css(q);c.css(q);b.wrap(n);b.parent().append(c);b.parent().append(g);a.railDraggable&&c.bind("mousedown",function(a){var b=f(document);y=!0;t=parseFloat(c.css("top"));pageY=a.pageY;b.bind("mousemove.slimscroll",function(a){currTop=t+a.pageY-pageY;c.css("top",currTop);m(0,c.position().top,!1)});
b.bind("mouseup.slimscroll",function(a){y=!1;p();b.unbind(".slimscroll")});return!1}).bind("selectstart.slimscroll",function(a){a.stopPropagation();a.preventDefault();return!1});g.hover(function(){v()},function(){p()});c.hover(function(){x=!0},function(){x=!1});b.hover(function(){s=!0;v();p()},function(){s=!1;p()});b.bind("touchstart",function(a,b){a.originalEvent.touches.length&&(z=a.originalEvent.touches[0].pageY)});b.bind("touchmove",function(b){k||b.originalEvent.preventDefault();b.originalEvent.touches.length&&
(m((z-b.originalEvent.touches[0].pageY)/a.touchScrollStep,!0),z=b.originalEvent.touches[0].pageY)});w();"bottom"===a.start?(c.css({top:b.outerHeight()-c.outerHeight()}),m(0,!0)):"top"!==a.start&&(m(f(a.start).position().top,null,!0),a.alwaysVisible||c.hide());C()}});return this}});jQuery.fn.extend({slimscroll:jQuery.fn.slimScroll})})(jQuery);;
(function($){var abs=Math.abs,max=Math.max,min=Math.min,round=Math.round;function div(){return $('<div/>')}$.imgAreaSelect=function(img,options){var $img=$(img),imgLoaded,$box=div(),$area=div(),$border=div().add(div()).add(div()).add(div()),$outer=div().add(div()).add(div()).add(div()),$handles=$([]),$areaOpera,left,top,imgOfs={left:0,top:0},imgWidth,imgHeight,$parent,parOfs={left:0,top:0},zIndex=0,position='absolute',startX,startY,scaleX,scaleY,resize,minWidth,minHeight,maxWidth,maxHeight,aspectRatio,shown,x1,y1,x2,y2,selection={x1:0,y1:0,x2:0,y2:0,width:0,height:0},docElem=document.documentElement,ua=navigator.userAgent,$p,d,i,o,w,h,adjusted;function viewX(x){return x+imgOfs.left-parOfs.left}function viewY(y){return y+imgOfs.top-parOfs.top}function selX(x){return x-imgOfs.left+parOfs.left}function selY(y){return y-imgOfs.top+parOfs.top}function evX(event){return event.pageX-parOfs.left}function evY(event){return event.pageY-parOfs.top}function getSelection(noScale){var sx=noScale||scaleX,sy=noScale||scaleY;return{x1:round(selection.x1*sx),y1:round(selection.y1*sy),x2:round(selection.x2*sx),y2:round(selection.y2*sy),width:round(selection.x2*sx)-round(selection.x1*sx),height:round(selection.y2*sy)-round(selection.y1*sy)}}function setSelection(x1,y1,x2,y2,noScale){var sx=noScale||scaleX,sy=noScale||scaleY;selection={x1:round(x1/sx||0),y1:round(y1/sy||0),x2:round(x2/sx||0),y2:round(y2/sy||0)};selection.width=selection.x2-selection.x1;selection.height=selection.y2-selection.y1}function adjust(){if(!imgLoaded||!$img.width())return;imgOfs={left:round($img.offset().left),top:round($img.offset().top)};imgWidth=$img.innerWidth();imgHeight=$img.innerHeight();imgOfs.top+=($img.outerHeight()-imgHeight)>>1;imgOfs.left+=($img.outerWidth()-imgWidth)>>1;minWidth=round(options.minWidth/scaleX)||0;minHeight=round(options.minHeight/scaleY)||0;maxWidth=round(min(options.maxWidth/scaleX||1<<24,imgWidth));maxHeight=round(min(options.maxHeight/scaleY||1<<24,imgHeight));if($().jquery=='1.3.2'&&position=='fixed'&&!docElem['getBoundingClientRect']){imgOfs.top+=max(document.body.scrollTop,docElem.scrollTop);imgOfs.left+=max(document.body.scrollLeft,docElem.scrollLeft)}parOfs=/absolute|relative/.test($parent.css('position'))?{left:round($parent.offset().left)-$parent.scrollLeft(),top:round($parent.offset().top)-$parent.scrollTop()}:position=='fixed'?{left:$(document).scrollLeft(),top:$(document).scrollTop()}:{left:0,top:0};left=viewX(0);top=viewY(0);if(selection.x2>imgWidth||selection.y2>imgHeight)doResize()}function update(resetKeyPress){if(!shown)return;$box.css({left:viewX(selection.x1),top:viewY(selection.y1)}).add($area).width(w=selection.width).height(h=selection.height);$area.add($border).add($handles).css({left:0,top:0});$border.width(max(w-$border.outerWidth()+$border.innerWidth(),0)).height(max(h-$border.outerHeight()+$border.innerHeight(),0));$($outer[0]).css({left:left,top:top,width:selection.x1,height:imgHeight});$($outer[1]).css({left:left+selection.x1,top:top,width:w,height:selection.y1});$($outer[2]).css({left:left+selection.x2,top:top,width:imgWidth-selection.x2,height:imgHeight});$($outer[3]).css({left:left+selection.x1,top:top+selection.y2,width:w,height:imgHeight-selection.y2});w-=$handles.outerWidth();h-=$handles.outerHeight();switch($handles.length){case 8:$($handles[4]).css({left:w>>1});$($handles[5]).css({left:w,top:h>>1});$($handles[6]).css({left:w>>1,top:h});$($handles[7]).css({top:h>>1});case 4:$handles.slice(1,3).css({left:w});$handles.slice(2,4).css({top:h})}if(resetKeyPress!==false){if($.imgAreaSelect.onKeyPress!=docKeyPress)$(document).unbind($.imgAreaSelect.keyPress,$.imgAreaSelect.onKeyPress);if(options.keys)$(document)[$.imgAreaSelect.keyPress]($.imgAreaSelect.onKeyPress=docKeyPress)}if(msie&&$border.outerWidth()-$border.innerWidth()==2){$border.css('margin',0);setTimeout(function(){$border.css('margin','auto')},0)}}function doUpdate(resetKeyPress){adjust();update(resetKeyPress);x1=viewX(selection.x1);y1=viewY(selection.y1);x2=viewX(selection.x2);y2=viewY(selection.y2)}function hide($elem,fn){options.fadeSpeed?$elem.fadeOut(options.fadeSpeed,fn):$elem.hide()}function areaMouseMove(event){var x=selX(evX(event))-selection.x1,y=selY(evY(event))-selection.y1;if(!adjusted){adjust();adjusted=true;$box.one('mouseout',function(){adjusted=false})}resize='';if(options.resizable){if(y<=options.resizeMargin)resize='n';else if(y>=selection.height-options.resizeMargin)resize='s';if(x<=options.resizeMargin)resize+='w';else if(x>=selection.width-options.resizeMargin)resize+='e'}$box.css('cursor',resize?resize+'-resize':options.movable?'move':'');if($areaOpera)$areaOpera.toggle()}function docMouseUp(event){$('body').css('cursor','');if(options.autoHide||selection.width*selection.height==0)hide($box.add($outer),function(){$(this).hide()});$(document).unbind('mousemove',selectingMouseMove);$box.mousemove(areaMouseMove);options.onSelectEnd(img,getSelection())}function areaMouseDown(event){if(event.which!=1)return false;adjust();if(resize){$('body').css('cursor',resize+'-resize');x1=viewX(selection[/w/.test(resize)?'x2':'x1']);y1=viewY(selection[/n/.test(resize)?'y2':'y1']);$(document).mousemove(selectingMouseMove).one('mouseup',docMouseUp);$box.unbind('mousemove',areaMouseMove)}else if(options.movable){startX=left+selection.x1-evX(event);startY=top+selection.y1-evY(event);$box.unbind('mousemove',areaMouseMove);$(document).mousemove(movingMouseMove).one('mouseup',function(){options.onSelectEnd(img,getSelection());$(document).unbind('mousemove',movingMouseMove);$box.mousemove(areaMouseMove)})}else $img.mousedown(event);return false}function fixAspectRatio(xFirst){if(aspectRatio)if(xFirst){x2=max(left,min(left+imgWidth,x1+abs(y2-y1)*aspectRatio*(x2>x1||-1)));y2=round(max(top,min(top+imgHeight,y1+abs(x2-x1)/aspectRatio*(y2>y1||-1))));x2=round(x2)}else{y2=max(top,min(top+imgHeight,y1+abs(x2-x1)/aspectRatio*(y2>y1||-1)));x2=round(max(left,min(left+imgWidth,x1+abs(y2-y1)*aspectRatio*(x2>x1||-1))));y2=round(y2)}}function doResize(){x1=min(x1,left+imgWidth);y1=min(y1,top+imgHeight);if(abs(x2-x1)<minWidth){x2=x1-minWidth*(x2<x1||-1);if(x2<left)x1=left+minWidth;else if(x2>left+imgWidth)x1=left+imgWidth-minWidth}if(abs(y2-y1)<minHeight){y2=y1-minHeight*(y2<y1||-1);if(y2<top)y1=top+minHeight;else if(y2>top+imgHeight)y1=top+imgHeight-minHeight}x2=max(left,min(x2,left+imgWidth));y2=max(top,min(y2,top+imgHeight));fixAspectRatio(abs(x2-x1)<abs(y2-y1)*aspectRatio);if(abs(x2-x1)>maxWidth){x2=x1-maxWidth*(x2<x1||-1);fixAspectRatio()}if(abs(y2-y1)>maxHeight){y2=y1-maxHeight*(y2<y1||-1);fixAspectRatio(true)}selection={x1:selX(min(x1,x2)),x2:selX(max(x1,x2)),y1:selY(min(y1,y2)),y2:selY(max(y1,y2)),width:abs(x2-x1),height:abs(y2-y1)};update();options.onSelectChange(img,getSelection())}function selectingMouseMove(event){x2=/w|e|^$/.test(resize)||aspectRatio?evX(event):viewX(selection.x2);y2=/n|s|^$/.test(resize)||aspectRatio?evY(event):viewY(selection.y2);doResize();return false}function doMove(newX1,newY1){x2=(x1=newX1)+selection.width;y2=(y1=newY1)+selection.height;$.extend(selection,{x1:selX(x1),y1:selY(y1),x2:selX(x2),y2:selY(y2)});update();options.onSelectChange(img,getSelection())}function movingMouseMove(event){x1=max(left,min(startX+evX(event),left+imgWidth-selection.width));y1=max(top,min(startY+evY(event),top+imgHeight-selection.height));doMove(x1,y1);event.preventDefault();return false}function startSelection(){$(document).unbind('mousemove',startSelection);adjust();x2=x1;y2=y1;doResize();resize='';if(!$outer.is(':visible'))$box.add($outer).hide().fadeIn(options.fadeSpeed||0);shown=true;$(document).unbind('mouseup',cancelSelection).mousemove(selectingMouseMove).one('mouseup',docMouseUp);$box.unbind('mousemove',areaMouseMove);options.onSelectStart(img,getSelection())}function cancelSelection(){$(document).unbind('mousemove',startSelection).unbind('mouseup',cancelSelection);hide($box.add($outer));setSelection(selX(x1),selY(y1),selX(x1),selY(y1));if(!(this instanceof $.imgAreaSelect)){options.onSelectChange(img,getSelection());options.onSelectEnd(img,getSelection())}}function imgMouseDown(event){if(event.which!=1||$outer.is(':animated'))return false;adjust();startX=x1=evX(event);startY=y1=evY(event);$(document).mousemove(startSelection).mouseup(cancelSelection);return false}function windowResize(){doUpdate(false)}function imgLoad(){imgLoaded=true;setOptions(options=$.extend({classPrefix:'imgareaselect',movable:true,parent:'body',resizable:true,resizeMargin:10,onInit:function(){},onSelectStart:function(){},onSelectChange:function(){},onSelectEnd:function(){}},options));$box.add($outer).css({visibility:''});if(options.show){shown=true;adjust();update();$box.add($outer).hide().fadeIn(options.fadeSpeed||0)}setTimeout(function(){options.onInit(img,getSelection())},0)}var docKeyPress=function(event){var k=options.keys,d,t,key=event.keyCode;d=!isNaN(k.alt)&&(event.altKey||event.originalEvent.altKey)?k.alt:!isNaN(k.ctrl)&&event.ctrlKey?k.ctrl:!isNaN(k.shift)&&event.shiftKey?k.shift:!isNaN(k.arrows)?k.arrows:10;if(k.arrows=='resize'||(k.shift=='resize'&&event.shiftKey)||(k.ctrl=='resize'&&event.ctrlKey)||(k.alt=='resize'&&(event.altKey||event.originalEvent.altKey))){switch(key){case 37:d=-d;case 39:t=max(x1,x2);x1=min(x1,x2);x2=max(t+d,x1);fixAspectRatio();break;case 38:d=-d;case 40:t=max(y1,y2);y1=min(y1,y2);y2=max(t+d,y1);fixAspectRatio(true);break;default:return}doResize()}else{x1=min(x1,x2);y1=min(y1,y2);switch(key){case 37:doMove(max(x1-d,left),y1);break;case 38:doMove(x1,max(y1-d,top));break;case 39:doMove(x1+min(d,imgWidth-selX(x2)),y1);break;case 40:doMove(x1,y1+min(d,imgHeight-selY(y2)));break;default:return}}return false};function styleOptions($elem,props){for(var option in props)if(options[option]!==undefined)$elem.css(props[option],options[option])}function setOptions(newOptions){if(newOptions.parent)($parent=$(newOptions.parent)).append($box.add($outer));$.extend(options,newOptions);adjust();if(newOptions.handles!=null){$handles.remove();$handles=$([]);i=newOptions.handles?newOptions.handles=='corners'?4:8:0;while(i--)$handles=$handles.add(div());$handles.addClass(options.classPrefix+'-handle').css({position:'absolute',fontSize:0,zIndex:zIndex+1||1});if(!parseInt($handles.css('width'))>=0)$handles.width(5).height(5);if(o=options.borderWidth)$handles.css({borderWidth:o,borderStyle:'solid'});styleOptions($handles,{borderColor1:'border-color',borderColor2:'background-color',borderOpacity:'opacity'})}scaleX=options.imageWidth/imgWidth||1;scaleY=options.imageHeight/imgHeight||1;if(newOptions.x1!=null){setSelection(newOptions.x1,newOptions.y1,newOptions.x2,newOptions.y2);newOptions.show=!newOptions.hide}if(newOptions.keys)options.keys=$.extend({shift:1,ctrl:'resize'},newOptions.keys);$outer.addClass(options.classPrefix+'-outer');$area.addClass(options.classPrefix+'-selection');for(i=0;i++<4;)$($border[i-1]).addClass(options.classPrefix+'-border'+i);styleOptions($area,{selectionColor:'background-color',selectionOpacity:'opacity'});styleOptions($border,{borderOpacity:'opacity',borderWidth:'border-width'});styleOptions($outer,{outerColor:'background-color',outerOpacity:'opacity'});if(o=options.borderColor1)$($border[0]).css({borderStyle:'solid',borderColor:o});if(o=options.borderColor2)$($border[1]).css({borderStyle:'dashed',borderColor:o});$box.append($area.add($border).add($areaOpera)).append($handles);if(msie){if(o=($outer.css('filter')||'').match(/opacity=(\d+)/))$outer.css('opacity',o[1]/100);if(o=($border.css('filter')||'').match(/opacity=(\d+)/))$border.css('opacity',o[1]/100)}if(newOptions.hide)hide($box.add($outer));else if(newOptions.show&&imgLoaded){shown=true;$box.add($outer).fadeIn(options.fadeSpeed||0);doUpdate()}aspectRatio=(d=(options.aspectRatio||'').split(/:/))[0]/d[1];$img.add($outer).unbind('mousedown',imgMouseDown);if(options.disable||options.enable===false){$box.unbind('mousemove',areaMouseMove).unbind('mousedown',areaMouseDown);$(window).unbind('resize',windowResize)}else{if(options.enable||options.disable===false){if(options.resizable||options.movable)$box.mousemove(areaMouseMove).mousedown(areaMouseDown);$(window).resize(windowResize)}if(!options.persistent)$img.add($outer).mousedown(imgMouseDown)}options.enable=options.disable=undefined}this.remove=function(){setOptions({disable:true});$box.add($outer).remove()};this.getOptions=function(){return options};this.setOptions=setOptions;this.getSelection=getSelection;this.setSelection=setSelection;this.cancelSelection=cancelSelection;this.update=doUpdate;var msie=(/msie ([\w.]+)/i.exec(ua)||[])[1],opera=/opera/i.test(ua),safari=/webkit/i.test(ua)&&!/chrome/i.test(ua);$p=$img;while($p.length){zIndex=max(zIndex,!isNaN($p.css('z-index'))?$p.css('z-index'):zIndex);if($p.css('position')=='fixed')position='fixed';$p=$p.parent(':not(body)')}zIndex=options.zIndex||zIndex;if(msie)$img.attr('unselectable','on');$.imgAreaSelect.keyPress=msie||safari?'keydown':'keypress';if(opera)$areaOpera=div().css({width:'100%',height:'100%',position:'absolute',zIndex:zIndex+2||2});$box.add($outer).css({visibility:'hidden',position:position,overflow:'hidden',zIndex:zIndex||'0'});$box.css({zIndex:zIndex+2||2});$area.add($border).css({position:'absolute',fontSize:0});img.complete||img.readyState=='complete'||!$img.is('img')?imgLoad():$img.one('load',imgLoad);if(!imgLoaded&&msie&&msie>=7)img.src=img.src};$.fn.imgAreaSelect=function(options){options=options||{};this.each(function(){if($(this).data('imgAreaSelect')){if(options.remove){$(this).data('imgAreaSelect').remove();$(this).removeData('imgAreaSelect')}else $(this).data('imgAreaSelect').setOptions(options)}else if(!options.remove){if(options.enable===undefined&&options.disable===undefined)options.enable=true;$(this).data('imgAreaSelect',new $.imgAreaSelect(this,options))}});if(options.instance)return $(this).data('imgAreaSelect');return this}})(jQuery);;
/*!jQuery Knob*/
/**
 * Downward compatible, touchable dial
 *
 * Version: 1.2.0 (15/07/2012)
 * Requires: jQuery v1.7+
 *
 * Copyright (c) 2012 Anthony Terrien
 * Under MIT and GPL licenses:
 *  http://www.opensource.org/licenses/mit-license.php
 *  http://www.gnu.org/licenses/gpl.html
 *
 * Thanks to vor, eskimoblood, spiffistan, FabrizioC
 */
(function ($) {

    /**
     * Kontrol library
     */
    "use strict";

    /**
     * Definition of globals and core
     */
    var k = {}, // kontrol
        max = Math.max,
        min = Math.min;

    k.c = {};
    k.c.d = $(document);
    k.c.t = function (e) {
        return e.originalEvent.touches.length - 1;
    };

    /**
     * Kontrol Object
     *
     * Definition of an abstract UI control
     *
     * Each concrete component must call this one.
     * <code>
     * k.o.call(this);
     * </code>
     */
    k.o = function () {
        var s = this;

        this.o = null; // array of options
        this.$ = null; // jQuery wrapped element
        this.i = null; // mixed HTMLInputElement or array of HTMLInputElement
        this.g = null; // 2D graphics context for 'pre-rendering'
        this.v = null; // value ; mixed array or integer
        this.cv = null; // change value ; not commited value
        this.x = 0; // canvas x position
        this.y = 0; // canvas y position
        this.$c = null; // jQuery canvas element
        this.c = null; // rendered canvas context
        this.t = 0; // touches index
        this.isInit = false;
        this.fgColor = null; // main color
        this.pColor = null; // previous color
        this.dH = null; // draw hook
        this.cH = null; // change hook
        this.eH = null; // cancel hook
        this.rH = null; // release hook

        this.run = function () {
            var cf = function (e, conf) {
                var k;
                for (k in conf) {
                    s.o[k] = conf[k];
                }
                s.init();
                s._configure()
                 ._draw();
            };

            if (this.$.data('kontroled')) return;
            this.$.data('kontroled', true);

            this.extend();
            this.o = $.extend(
                {
                    // Config
                    min: this.$.data('min') || 0,
                    max: this.$.data('max') || 100,
                    stopper: true,
                    readOnly: this.$.data('readonly'),

                    // UI
                    cursor: (this.$.data('cursor') === true && 30)
                                || this.$.data('cursor')
                                || 0,
                    thickness: this.$.data('thickness') || 0.35,
                    lineCap: this.$.data('linecap') || 'butt',
                    width: this.$.data('width') || 80,
                    height: this.$.data('height') || 80,
                    displayInput: this.$.data('displayinput') == null || this.$.data('displayinput'),
                    displayPrevious: this.$.data('displayprevious'),
                    fgColor: this.$.data('fgcolor') || '#87CEEB',
                    inputColor: this.$.data('inputcolor') || this.$.data('fgcolor') || '#87CEEB',
                    inline: false,
                    step: this.$.data('step') || 1,

                    // Hooks
                    draw: null, // function () {}
                    change: null, // function (value) {}
                    cancel: null, // function () {}
                    release: null // function (value) {}
                }, this.o
            );

            // routing value
            if (this.$.is('fieldset')) {

                // fieldset = array of integer
                this.v = {};
                this.i = this.$.find('input')
                this.i.each(function (k) {
                    var $this = $(this);
                    s.i[k] = $this;
                    s.v[k] = $this.val();

                    $this.bind(
                        'change'
                        , function () {
                            var val = {};
                            val[k] = $this.val();
                            s.val(val);
                        }
                    );
                });
                this.$.find('legend').remove();

            } else {
                // input = integer
                this.i = this.$;
                this.v = this.$.val();
                (this.v == '') && (this.v = this.o.min);

                this.$.bind(
                    'change'
                    , function () {
                        s.val(s._validate(s.$.val()));
                    }
                );
            }

            (!this.o.displayInput) && this.$.hide();

            this.$c = $('<canvas width="' +
                            this.o.width + 'px" height="' +
                            this.o.height + 'px"></canvas>');
            this.c = this.$c[0].getContext("2d");

            this.$
                .wrap($('<div style="' + (this.o.inline ? 'display:inline;' : '') +
                        'width:' + this.o.width + 'px;height:' +
                        this.o.height + 'px;"></div>'))
                .before(this.$c);

            if (this.v instanceof Object) {
                this.cv = {};
                this.copy(this.v, this.cv);
            } else {
                this.cv = this.v;
            }

            this.$
                .bind("configure", cf)
                .parent()
                .bind("configure", cf);

            this._listen()
                ._configure()
                ._xy()
                .init();

            this.isInit = true;

            this._draw();

            return this;
        };

        this._draw = function () {

            // canvas pre-rendering
            var d = true,
                c = document.createElement('canvas');

            c.width = s.o.width;
            c.height = s.o.height;
            s.g = c.getContext('2d');

            s.clear();

            s.dH
            && (d = s.dH());

            (d !== false) && s.draw();

            s.c.drawImage(c, 0, 0);
            c = null;
        };

        this._touch = function (e) {

            var touchMove = function (e) {

                var v = s.xy2val(
                            e.originalEvent.touches[s.t].pageX,
                            e.originalEvent.touches[s.t].pageY
                            );

                if (v == s.cv) return;

                if (
                    s.cH
                    && (s.cH(v) === false)
                ) return;


                s.change(s._validate(v));
                s._draw();
            };

            // get touches index
            this.t = k.c.t(e);

            // First touch
            touchMove(e);

            // Touch events listeners
            k.c.d
                .bind("touchmove.k", touchMove)
                .bind(
                    "touchend.k"
                    , function () {
                        k.c.d.unbind('touchmove.k touchend.k');

                        if (
                            s.rH
                            && (s.rH(s.cv) === false)
                        ) return;

                        s.val(s.cv);
                    }
                );

            return this;
        };

        this._mouse = function (e) {

            var mouseMove = function (e) {
                var v = s.xy2val(e.pageX, e.pageY);
                if (v == s.cv) return;

                if (
                    s.cH
                    && (s.cH(v) === false)
                ) return;

                s.change(s._validate(v));
                s._draw();
            };

            // First click
            mouseMove(e);

            // Mouse events listeners
            k.c.d
                .bind("mousemove.k", mouseMove)
                .bind(
                    // Escape key cancel current change
                    "keyup.k"
                    , function (e) {
                        if (e.keyCode === 27) {
                            k.c.d.unbind("mouseup.k mousemove.k keyup.k");

                            if (
                                s.eH
                                && (s.eH() === false)
                            ) return;

                            s.cancel();
                        }
                    }
                )
                .bind(
                    "mouseup.k"
                    , function (e) {
                        k.c.d.unbind('mousemove.k mouseup.k keyup.k');

                        if (
                            s.rH
                            && (s.rH(s.cv) === false)
                        ) return;

                        s.val(s.cv);
                    }
                );

            return this;
        };

        this._xy = function () {
            var o = this.$c.offset();
            this.x = o.left;
            this.y = o.top;
            return this;
        };

        this._listen = function () {

            if (!this.o.readOnly) {
                this.$c
                    .bind(
                        "mousedown"
                        , function (e) {
                            e.preventDefault();
                            s._xy()._mouse(e);
                        }
                    )
                    .bind(
                        "touchstart"
                        , function (e) {
                            e.preventDefault();
                            s._xy()._touch(e);
                        }
                    );
                this.listen();
            } else {
                this.$.attr('readonly', 'readonly');
            }

            return this;
        };

        this._configure = function () {

            // Hooks
            if (this.o.draw) this.dH = this.o.draw;
            if (this.o.change) this.cH = this.o.change;
            if (this.o.cancel) this.eH = this.o.cancel;
            if (this.o.release) this.rH = this.o.release;

            if (this.o.displayPrevious) {
                this.pColor = this.h2rgba(this.o.fgColor, "0.4");
                this.fgColor = this.h2rgba(this.o.fgColor, "0.6");
            } else {
                this.fgColor = this.o.fgColor;
            }

            return this;
        };

        this._clear = function () {
            this.$c[0].width = this.$c[0].width;
        };

        this._validate = function (v) {
            return (~~(((v < 0) ? -0.5 : 0.5) + (v / this.o.step))) * this.o.step;
        };

        // Abstract methods
        this.listen = function () { }; // on start, one time
        this.extend = function () { }; // each time configure triggered
        this.init = function () { }; // each time configure triggered
        this.change = function (v) { }; // on change
        this.val = function (v) { }; // on release
        this.xy2val = function (x, y) { }; //
        this.draw = function () { }; // on change / on release
        this.clear = function () { this._clear(); };

        // Utils
        this.h2rgba = function (h, a) {
            var rgb;
            h = h.substring(1, 7)
            rgb = [parseInt(h.substring(0, 2), 16)
                   , parseInt(h.substring(2, 4), 16)
                   , parseInt(h.substring(4, 6), 16)];
            return "rgba(" + rgb[0] + "," + rgb[1] + "," + rgb[2] + "," + a + ")";
        };

        this.copy = function (f, t) {
            for (var i in f) { t[i] = f[i]; }
        };
    };


    /**
     * k.Dial
     */
    k.Dial = function () {
        k.o.call(this);

        this.startAngle = null;
        this.xy = null;
        this.radius = null;
        this.lineWidth = null;
        this.cursorExt = null;
        this.w2 = null;
        this.PI2 = 2 * Math.PI;

        this.extend = function () {
            this.o = $.extend(
                {
                    bgColor: this.$.data('bgcolor') || '#EEEEEE',
                    angleOffset: this.$.data('angleoffset') || 0,
                    angleArc: this.$.data('anglearc') || 360,
                    inline: true
                }, this.o
            );
        };

        this.val = function (v) {
            if (null != v) {
                this.cv = this.o.stopper ? max(min(v, this.o.max), this.o.min) : v;
                this.v = this.cv;
                this.$.val(this.v);
                this._draw();
            } else {
                return this.v;
            }
        };

        this.xy2val = function (x, y) {
            var a, ret;

            a = Math.atan2(
                        x - (this.x + this.w2)
                        , -(y - this.y - this.w2)
                    ) - this.angleOffset;

            if (this.angleArc != this.PI2 && (a < 0) && (a > -0.5)) {
                // if isset angleArc option, set to min if .5 under min
                a = 0;
            } else if (a < 0) {
                a += this.PI2;
            }

            ret = ~~(0.5 + (a * (this.o.max - this.o.min) / this.angleArc))
                    + this.o.min;

            this.o.stopper
            && (ret = max(min(ret, this.o.max), this.o.min));

            return ret;
        };

        this.listen = function () {
            // bind MouseWheel
            var s = this,
                mw = function (e) {
                    e.preventDefault();
                    var ori = e.originalEvent
                        , deltaX = ori.detail || ori.wheelDeltaX
                        , deltaY = ori.detail || ori.wheelDeltaY
                        , v = parseInt(s.$.val()) + (deltaX > 0 || deltaY > 0 ? s.o.step : deltaX < 0 || deltaY < 0 ? -s.o.step : 0);

                    if (
                        s.cH
                        && (s.cH(v) === false)
                    ) return;

                    s.val(v);
                }
                , kval, to, m = 1, kv = { 37: -s.o.step, 38: s.o.step, 39: s.o.step, 40: -s.o.step };

            this.$
                .bind(
                    "keydown"
                    , function (e) {
                        var kc = e.keyCode;

                        // numpad support
                        if (kc >= 96 && kc <= 105) {
                            kc = e.keyCode = kc - 48;
                        }

                        kval = parseInt(String.fromCharCode(kc));

                        if (isNaN(kval)) {

                            (kc !== 13)         // enter
                            && (kc !== 8)       // bs
                            && (kc !== 9)       // tab
                            && (kc !== 189)     // -
                            && e.preventDefault();

                            // arrows
                            if ($.inArray(kc, [37, 38, 39, 40]) > -1) {
                                e.preventDefault();

                                var v = parseInt(s.$.val()) + kv[kc] * m;

                                s.o.stopper
                                && (v = max(min(v, s.o.max), s.o.min));

                                s.change(v);
                                s._draw();

                                // long time keydown speed-up
                                to = window.setTimeout(
                                    function () { m *= 2; }
                                    , 30
                                );
                            }
                        }
                    }
                )
                .bind(
                    "keyup"
                    , function (e) {
                        if (isNaN(kval)) {
                            if (to) {
                                window.clearTimeout(to);
                                to = null;
                                m = 1;
                                s.val(s.$.val());
                            }
                        } else {
                            // kval postcond
                            (s.$.val() > s.o.max && s.$.val(s.o.max))
                            || (s.$.val() < s.o.min && s.$.val(s.o.min));
                        }

                    }
                );

            this.$c.bind("mousewheel DOMMouseScroll", mw);
            this.$.bind("mousewheel DOMMouseScroll", mw)
        };

        this.init = function () {

            if (
                this.v < this.o.min
                || this.v > this.o.max
            ) this.v = this.o.min;

            this.$.val(this.v);
            this.w2 = this.o.width / 2;
            this.cursorExt = this.o.cursor / 100;
            this.xy = this.w2;
            this.lineWidth = this.xy * this.o.thickness;
            this.lineCap = this.o.lineCap;
            this.radius = this.xy - this.lineWidth / 2;

            this.o.angleOffset
            && (this.o.angleOffset = isNaN(this.o.angleOffset) ? 0 : this.o.angleOffset);

            this.o.angleArc
            && (this.o.angleArc = isNaN(this.o.angleArc) ? this.PI2 : this.o.angleArc);

            // deg to rad
            this.angleOffset = this.o.angleOffset * Math.PI / 180;
            this.angleArc = this.o.angleArc * Math.PI / 180;

            // compute start and end angles
            this.startAngle = 1.5 * Math.PI + this.angleOffset;
            this.endAngle = 1.5 * Math.PI + this.angleOffset + this.angleArc;

            var s = max(
                            String(Math.abs(this.o.max)).length
                            , String(Math.abs(this.o.min)).length
                            , 2
                            ) + 2;

            this.o.displayInput
                && this.i.css({
                    'width': ((this.o.width / 2 + 4) >> 0) + 'px'
                        , 'height': ((this.o.width / 3) >> 0) + 'px'
                        , 'position': 'absolute'
                        , 'vertical-align': 'middle'
                        , 'margin-top': ((this.o.width / 3) >> 0) + 'px'
                        , 'margin-left': '-' + ((this.o.width * 3 / 4 + 2) >> 0) + 'px'
                        , 'border': 0
                        , 'background': 'none'
                        , 'font': 'bold ' + ((this.o.width / s) >> 0) + 'px Arial'
                        , 'text-align': 'center'
                        , 'color': this.o.inputColor || this.o.fgColor
                        , 'padding': '0px'
                        , '-webkit-appearance': 'none'
                })
                || this.i.css({
                    'width': '0px'
                        , 'visibility': 'hidden'
                });
        };

        this.change = function (v) {
            this.cv = v;
            this.$.val(v);
        };

        this.angle = function (v) {
            return (v - this.o.min) * this.angleArc / (this.o.max - this.o.min);
        };

        this.draw = function () {

            var c = this.g,                 // context
                a = this.angle(this.cv)    // Angle
                , sat = this.startAngle     // Start angle
                , eat = sat + a             // End angle
                , sa, ea                    // Previous angles
                , r = 1;

            c.lineWidth = this.lineWidth;

            c.lineCap = this.lineCap;

            this.o.cursor
                && (sat = eat - this.cursorExt)
                && (eat = eat + this.cursorExt);

            c.beginPath();
            c.strokeStyle = this.o.bgColor;
            c.arc(this.xy, this.xy, this.radius, this.endAngle, this.startAngle, true);
            c.stroke();

            if (this.o.displayPrevious) {
                ea = this.startAngle + this.angle(this.v);
                sa = this.startAngle;
                this.o.cursor
                    && (sa = ea - this.cursorExt)
                    && (ea = ea + this.cursorExt);

                c.beginPath();
                c.strokeStyle = this.pColor;
                c.arc(this.xy, this.xy, this.radius, sa, ea, false);
                c.stroke();
                r = (this.cv == this.v);
            }

            c.beginPath();
            c.strokeStyle = r ? this.o.fgColor : this.fgColor;
            c.arc(this.xy, this.xy, this.radius, sat, eat, false);
            c.stroke();
        };

        this.cancel = function () {
            this.val(this.v);
        };
    };

    $.fn.dial = $.fn.knob = function (o) {
        return this.each(
            function () {
                var d = new k.Dial();
                d.o = o;
                d.$ = $(this);
                d.run();
            }
        ).parent();
    };

})(jQuery);;
/**
 * Cookie plugin
 *
 * Copyright (c) 2006 Klaus Hartl (stilbuero.de)
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 *
 */

/**
 * Create a cookie with the given name and value and other optional parameters.
 *
 * @example $.cookie('the_cookie', 'the_value');
 * @desc Set the value of a cookie.
 * @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true });
 * @desc Create a cookie with all available options.
 * @example $.cookie('the_cookie', 'the_value');
 * @desc Create a session cookie.
 * @example $.cookie('the_cookie', null);
 * @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain
 *       used when the cookie was set.
 *
 * @param String name The name of the cookie.
 * @param String value The value of the cookie.
 * @param Object options An object literal containing key/value pairs to provide optional cookie attributes.
 * @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object.
 *                             If a negative value is specified (e.g. a date in the past), the cookie will be deleted.
 *                             If set to null or omitted, the cookie will be a session cookie and will not be retained
 *                             when the the browser exits.
 * @option String path The value of the path atribute of the cookie (default: path of page that created the cookie).
 * @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie).
 * @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will
 *                        require a secure protocol (like HTTPS).
 * @type undefined
 *
 * @name $.cookie
 * @cat Plugins/Cookie
 * @author Klaus Hartl/klaus.hartl@stilbuero.de
 */

/**
 * Get the value of a cookie with the given name.
 *
 * @example $.cookie('the_cookie');
 * @desc Get the value of a cookie.
 *
 * @param String name The name of the cookie.
 * @return The value of the cookie.
 * @type String
 *
 * @name $.cookie
 * @cat Plugins/Cookie
 * @author Klaus Hartl/klaus.hartl@stilbuero.de
 */
jQuery.cookie = function(name, value, options) {
    if (typeof value != 'undefined') { // name and value given, set cookie
        options = options || {};
        if (value === null) {
            value = '';
            options.expires = -1;
        }
        var expires = '';
        if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
            var date;
            if (typeof options.expires == 'number') {
                date = new Date();
                date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
            } else {
                date = options.expires;
            }
            expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
        }
        // CAUTION: Needed to parenthesize options.path and options.domain
        // in the following expressions, otherwise they evaluate to undefined
        // in the packed version for some reason...
        var path = options.path ? '; path=' + (options.path) : '';
        var domain = options.domain ? '; domain=' + (options.domain) : '';
        var secure = options.secure ? '; secure' : '';
        document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
    } else { // only name given, get cookie
        var cookieValue = null;
        if (document.cookie && document.cookie != '') {
            var cookies = document.cookie.split(';');
            for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) == (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }
};;
/*
 * jQuery FlyOut
 *
 * version 0.21 (July 21, 2008)
 * version 0.22 (July 22, 2008) notes: minor reordering to loadingSrc logic.
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 */

/**
 * The flyout() method provides an alternate means of loading and display sub-content
 * with a nifty flyout animation technique.
 * Currently, flyout only supports img sub-content.
 *
 * flyout() takes a single object argument:  $().flyout({param:setting, etc..})
 *
 * Settings:
 *
 *			outSpeed:	speed in milliseconds for the flyout animation - default: 1000
 *
 *			inSpeed:	speed in milliseconds for the flyback animation - default: 500
 *
 *			outEase:	the easing method to use for the flyout animation - default: swing
 *
 *			inEase:		the easing method to use for the flyback animation - default: swing
 *			
 *			loadingSrc: the image file to use while an image is being loaded prior to flyout
 *						default: none
 *						
 *			loader: 	the ID for the created flyout div that contains the sub-content
 *						this is currently only useful for multiple skinnings via CSS
 *						default: 'loader'
 *
 *			loaderZIndex: the CSS z-index for the flyout
 *						default: 500
 *
 *			widthMargin: the left and right margin space for the final flyout
 *						this value is effectively divided between the left and right margin
 *						default: 40
 *			
 *			heightMargin: the top and bottom margin space for the final flyout
 *						this value is effectively divided between the top and bottom margin
 *						default: 40
 *
 * For more details see: http://nixbox.com/demos/jquery.flyout.php
 *
 * @example $('.thumb').flyout();
 * @desc standard flyouts applied to all elements with the 'thumbs' class. 
 * 
 * @example $('.thumb').flyout({loadingSrc:'images/thumb-loading.gif',
								outEase:'easeOutCirc',
								inEase:'easeOutBounce'});
 * @desc flyouts created with different ease in and ease out and a loading animation image is specified
 *
 * @name flyout
 * @type jQuery
 * @param Object options Options which control the flyout animation and content
 * @cat Plugins/Flyout
 * @return jQuery
 * @author Jolyon Terwilliger (jolyon@nixbox.com)
 */


$.fn.extend({flyout : function(options) {
		var shown = false;
		var animating = false;
		var holder;
		var thumb;
		var tloc;
		var th;
		var tw;
		var bigimg = new Image();
		var subType = 'img';
		var offset;
	
		this.click(function() {
			if (animating == true) { return false; }
	
			if (shown) { putAway(this); }
			else { flyOut(this); }
	
			return false;
		});			
		
		var o = jQuery.extend({
			outSpeed : 1000,
			inSpeed : 500,
			outEase : 'swing',
			inEase : 'swing',
			loadingSrc: null,
			loader: 'loader',
			loaderZIndex: 5055555550,
			widthMargin: 40,
			heightMargin: 40
		}, options);
	
		function flyOut(it) {
			animating = true;
			
			holder = $(it);
			thumb = $('img',it);
			bigimg = new Image(); 
			sL = $(window).scrollLeft();
			sT = $(window).scrollTop();
			tloc = thumb.offset();
			th = thumb.height();
			tw = thumb.width();
			$('<div></div>').attr('id',o.loader)
							.appendTo('body')
							.css({'position':'absolute',
								'top':tloc.top,
								'left':tloc.left,
								'height':th,
								'width':tw,
								'opacity':.5,
								'display':'block',
								'z-index':o.loaderZIndex});
			if (o.loadingSrc) {
				$('#'+o.loader).append($('<img/>').load(function() {
													$(this)
														.css({'position':'relative',
															 'top':th/2-(this.height/2),
															 'left':tw/2-(this.width/2)})
														.attr('alt','Loading...Please wait');
													})
												.attr('src',o.loadingSrc)
										);
			}
			else {
				$('#'+o.loader).css('background-color','#000')
								.append($('<span></span>').text('loading')
															.css({'position':'relative',
																 'top':'2px',
																 'left':'2px',
																 'color':'#FFF',
																 'font-size':'9px'})
									 	);
			}
			$(bigimg).load(function () {
			    var close = holder.data('titleclose');

			    if (!close && typeof getRes != "undefined") {
			        close = getRes("click_to_close");
			    }
			    
				imgtag = $('<img />').attr('src', holder.attr('href')).attr('title', close).height(th).width(tw);
	
				max_x = $(window).width()-o.widthMargin;
				max_y = $(window).height()-o.heightMargin;
				
				width = bigimg.width;
				height = bigimg.height;
	
				x_dim = max_x / width;
				y_dim = max_y / height;
	
				if (x_dim <=y_dim) {
					y_dim = x_dim;
				} else {
					x_dim = y_dim;
				}
				
				dw = Math.round(width  * x_dim);
				dh = Math.round(height * y_dim);
				if (dw>width) {dw = width}
				if (dh>height) {dh = height}
				dl = Math.round(($(window).width()/2)-(dw/2)+sL);
				dt = Math.round(($(window).height()/2)-(dh/2)+sT);
	
				$('#'+o.loader).empty().css('opacity',1).append(imgtag).width('auto').height('auto').animate({top:dt, left:dl},{duration:o.outSpeed, queue:false, easing:o.outEase});
				$('#'+o.loader+' '+subType).animate({height:dh, width:dw}, o.outSpeed, o.outEase, function() { 	
																					shown = true;
																					animating=false;
																					$('#'+o.loader+' '+subType).click(function(){putAway(null)})
																				});
			});
			bigimg.src = holder.attr('href');
		}
	
	
		function putAway(next) {
			// not necessary right now, but jic.
			if (animating == true || shown == false) {return false;}
			
			animating = true;
			$('#'+o.loader).animate({top:tloc.top, left:tloc.left},{duration:o.inSpeed, queue:false, easing:o.inEase});
			$('#'+o.loader+' '+subType).animate({height:th, width:tw}, o.inSpeed, o.inEase, function() {
																		 $('#'+o.loader).css('display','none').remove(); 
																		 shown = false;
																		 animating=false;
																		 bigimg=null;			
																		 if (next) {
																			flyOut(next);
																		 }
			});
		}
		
		return this;
		
	}
});
;
/*
 * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/
 *
 * Uses the built in easing capabilities added In jQuery 1.1
 * to offer multiple easing options
 *
 * TERMS OF USE - jQuery Easing
 * 
 * Open source under the BSD License. 
 * 
 * Copyright © 2008 George McGinley Smith
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 * 
 * Redistributions of source code must retain the above copyright notice, this list of 
 * conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, this list 
 * of conditions and the following disclaimer in the documentation and/or other materials 
 * provided with the distribution.
 * 
 * Neither the name of the author nor the names of contributors may be used to endorse 
 * or promote products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 *  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
 * OF THE POSSIBILITY OF SUCH DAMAGE. 
 *
*/

// t: current time, b: begInnIng value, c: change In value, d: duration
jQuery.easing['jswing'] = jQuery.easing['swing'];

jQuery.extend( jQuery.easing,
{
	def: 'easeOutQuad',
	swing: function (x, t, b, c, d) {
		//alert(jQuery.easing.default);
		return jQuery.easing[jQuery.easing.def](x, t, b, c, d);
	},
	easeInQuad: function (x, t, b, c, d) {
		return c*(t/=d)*t + b;
	},
	easeOutQuad: function (x, t, b, c, d) {
		return -c *(t/=d)*(t-2) + b;
	},
	easeInOutQuad: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t + b;
		return -c/2 * ((--t)*(t-2) - 1) + b;
	},
	easeInCubic: function (x, t, b, c, d) {
		return c*(t/=d)*t*t + b;
	},
	easeOutCubic: function (x, t, b, c, d) {
		return c*((t=t/d-1)*t*t + 1) + b;
	},
	easeInOutCubic: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t*t + b;
		return c/2*((t-=2)*t*t + 2) + b;
	},
	easeInQuart: function (x, t, b, c, d) {
		return c*(t/=d)*t*t*t + b;
	},
	easeOutQuart: function (x, t, b, c, d) {
		return -c * ((t=t/d-1)*t*t*t - 1) + b;
	},
	easeInOutQuart: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
		return -c/2 * ((t-=2)*t*t*t - 2) + b;
	},
	easeInQuint: function (x, t, b, c, d) {
		return c*(t/=d)*t*t*t*t + b;
	},
	easeOutQuint: function (x, t, b, c, d) {
		return c*((t=t/d-1)*t*t*t*t + 1) + b;
	},
	easeInOutQuint: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
		return c/2*((t-=2)*t*t*t*t + 2) + b;
	},
	easeInSine: function (x, t, b, c, d) {
		return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
	},
	easeOutSine: function (x, t, b, c, d) {
		return c * Math.sin(t/d * (Math.PI/2)) + b;
	},
	easeInOutSine: function (x, t, b, c, d) {
		return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
	},
	easeInExpo: function (x, t, b, c, d) {
		return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
	},
	easeOutExpo: function (x, t, b, c, d) {
		return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
	},
	easeInOutExpo: function (x, t, b, c, d) {
		if (t==0) return b;
		if (t==d) return b+c;
		if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
		return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
	},
	easeInCirc: function (x, t, b, c, d) {
		return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
	},
	easeOutCirc: function (x, t, b, c, d) {
		return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
	},
	easeInOutCirc: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
		return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
	},
	easeInElastic: function (x, t, b, c, d) {
		var s=1.70158;var p=0;var a=c;
		if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
		if (a < Math.abs(c)) { a=c; var s=p/4; }
		else var s = p/(2*Math.PI) * Math.asin (c/a);
		return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
	},
	easeOutElastic: function (x, t, b, c, d) {
		var s=1.70158;var p=0;var a=c;
		if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
		if (a < Math.abs(c)) { a=c; var s=p/4; }
		else var s = p/(2*Math.PI) * Math.asin (c/a);
		return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
	},
	easeInOutElastic: function (x, t, b, c, d) {
		var s=1.70158;var p=0;var a=c;
		if (t==0) return b;  if ((t/=d/2)==2) return b+c;  if (!p) p=d*(.3*1.5);
		if (a < Math.abs(c)) { a=c; var s=p/4; }
		else var s = p/(2*Math.PI) * Math.asin (c/a);
		if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
		return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
	},
	easeInBack: function (x, t, b, c, d, s) {
		if (s == undefined) s = 1.70158;
		return c*(t/=d)*t*((s+1)*t - s) + b;
	},
	easeOutBack: function (x, t, b, c, d, s) {
		if (s == undefined) s = 1.70158;
		return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
	},
	easeInOutBack: function (x, t, b, c, d, s) {
		if (s == undefined) s = 1.70158; 
		if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
		return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
	},
	easeInBounce: function (x, t, b, c, d) {
		return c - jQuery.easing.easeOutBounce (x, d-t, 0, c, d) + b;
	},
	easeOutBounce: function (x, t, b, c, d) {
		if ((t/=d) < (1/2.75)) {
			return c*(7.5625*t*t) + b;
		} else if (t < (2/2.75)) {
			return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
		} else if (t < (2.5/2.75)) {
			return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
		} else {
			return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
		}
	},
	easeInOutBounce: function (x, t, b, c, d) {
		if (t < d/2) return jQuery.easing.easeInBounce (x, t*2, 0, c, d) * .5 + b;
		return jQuery.easing.easeOutBounce (x, t*2-d, 0, c, d) * .5 + c*.5 + b;
	}
});

/*
 *
 * TERMS OF USE - EASING EQUATIONS
 * 
 * Open source under the BSD License. 
 * 
 * Copyright © 2001 Robert Penner
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without modification, 
 * are permitted provided that the following conditions are met:
 * 
 * Redistributions of source code must retain the above copyright notice, this list of 
 * conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice, this list 
 * of conditions and the following disclaimer in the documentation and/or other materials 
 * provided with the distribution.
 * 
 * Neither the name of the author nor the names of contributors may be used to endorse 
 * or promote products derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY 
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *  COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 *  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 *  GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 
 * OF THE POSSIBILITY OF SUCH DAMAGE. 
 *
 */;
/*! Lazy Load 1.9.3 - MIT license - Copyright 2010-2013 Mika Tuupola */
!function(a,b,c,d){var e=a(b);a.fn.lazyload=function(f){function g(){var b=0;i.each(function(){var c=a(this);if(!j.skip_invisible||c.is(":visible"))if(a.abovethetop(this,j)||a.leftofbegin(this,j));else if(a.belowthefold(this,j)||a.rightoffold(this,j)){if(++b>j.failure_limit)return!1}else c.trigger("appear"),b=0})}var h,i=this,j={threshold:0,failure_limit:0,event:"scroll",effect:"show",container:b,data_attribute:"original",skip_invisible:!0,appear:null,load:null,placeholder:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC"};return f&&(d!==f.failurelimit&&(f.failure_limit=f.failurelimit,delete f.failurelimit),d!==f.effectspeed&&(f.effect_speed=f.effectspeed,delete f.effectspeed),a.extend(j,f)),h=j.container===d||j.container===b?e:a(j.container),0===j.event.indexOf("scroll")&&h.bind(j.event,function(){return g()}),this.each(function(){var b=this,c=a(b);b.loaded=!1,(c.attr("src")===d||c.attr("src")===!1)&&c.is("img")&&c.attr("src",j.placeholder),c.one("appear",function(){if(!this.loaded){if(j.appear){var d=i.length;j.appear.call(b,d,j)}a("<img />").bind("load",function(){var d=c.attr("data-"+j.data_attribute);c.hide(),c.is("img")?c.attr("src",d):c.css("background-image","url('"+d+"')"),c[j.effect](j.effect_speed),b.loaded=!0;var e=a.grep(i,function(a){return!a.loaded});if(i=a(e),j.load){var f=i.length;j.load.call(b,f,j)}}).attr("src",c.attr("data-"+j.data_attribute))}}),0!==j.event.indexOf("scroll")&&c.bind(j.event,function(){b.loaded||c.trigger("appear")})}),e.bind("resize",function(){g()}),/(?:iphone|ipod|ipad).*os 5/gi.test(navigator.appVersion)&&e.bind("pageshow",function(b){b.originalEvent&&b.originalEvent.persisted&&i.each(function(){a(this).trigger("appear")})}),a(c).ready(function(){g()}),this},a.belowthefold=function(c,f){var g;return g=f.container===d||f.container===b?(b.innerHeight?b.innerHeight:e.height())+e.scrollTop():a(f.container).offset().top+a(f.container).height(),g<=a(c).offset().top-f.threshold},a.rightoffold=function(c,f){var g;return g=f.container===d||f.container===b?e.width()+e.scrollLeft():a(f.container).offset().left+a(f.container).width(),g<=a(c).offset().left-f.threshold},a.abovethetop=function(c,f){var g;return g=f.container===d||f.container===b?e.scrollTop():a(f.container).offset().top,g>=a(c).offset().top+f.threshold+a(c).height()},a.leftofbegin=function(c,f){var g;return g=f.container===d||f.container===b?e.scrollLeft():a(f.container).offset().left,g>=a(c).offset().left+f.threshold+a(c).width()},a.inviewport=function(b,c){return!(a.rightoffold(b,c)||a.leftofbegin(b,c)||a.belowthefold(b,c)||a.abovethetop(b,c))},a.extend(a.expr[":"],{"below-the-fold":function(b){return a.belowthefold(b,{threshold:0})},"above-the-top":function(b){return!a.belowthefold(b,{threshold:0})},"right-of-screen":function(b){return a.rightoffold(b,{threshold:0})},"left-of-screen":function(b){return!a.rightoffold(b,{threshold:0})},"in-viewport":function(b){return a.inviewport(b,{threshold:0})},"above-the-fold":function(b){return!a.belowthefold(b,{threshold:0})},"right-of-fold":function(b){return a.rightoffold(b,{threshold:0})},"left-of-fold":function(b){return!a.rightoffold(b,{threshold:0})}})}(jQuery,window,document);;
/*
 * jQuery Tooltip plugin 1.2
 *
 * http://bassistance.de/jquery-plugins/jquery-plugin-tooltip/
 * http://docs.jquery.com/Plugins/Tooltip
 *
 * Copyright (c) 2006 - 2008 Jörn Zaefferer
 *
 * $Id: jquery.tooltip.js 4569 2008-01-31 19:36:35Z joern.zaefferer $
 * 
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 */
 
;(function($) {
	
		// the tooltip element
	var helper = {},
		// the current tooltipped element
		current,
		// the title of the current element, used for restoring
		title,
		// timeout id for delayed tooltips
		tID,
		// IE 5.5 or 6
		IE = $.browser.msie && /MSIE\s(5\.5|6\.)/.test(navigator.userAgent),
		// flag for mouse tracking
		track = false;
	
	$.tooltip = {
		blocked: false,
		defaults: {
			delay: 200,
			showURL: true,
			extraClass: "",
			top: 15,
			left: -30,
			id: "tooltip"
		},
		block: function() {
			$.tooltip.blocked = !$.tooltip.blocked;
		}
	};
	
	$.fn.extend({
		tooltip: function(settings) {
			settings = $.extend({}, $.tooltip.defaults, settings);
			createHelper(settings);
			return this.each(function() {
					$.data(this, "tooltip-settings", settings);
					// copy tooltip into its own expando and remove the title
					this.tooltipText = this.title;
					$(this).removeAttr("title");
					// also remove alt attribute to prevent default tooltip in IE
					this.alt = "";
				})
				.hover(save, hide)
				.click(hide);
		},
		fixPNG: IE ? function() {
			return this.each(function () {
				var image = $(this).css('backgroundImage');
				if (image.match(/^url\(["']?(.*\.png)["']?\)$/i)) {
					image = RegExp.$1;
					$(this).css({
						'backgroundImage': 'none',
						'filter': "progid:DXImageTransform.Microsoft.AlphaImageLoader(enabled=true, sizingMethod=crop, src='" + image + "')"
					}).each(function () {
						var position = $(this).css('position');
						if (position != 'absolute' && position != 'relative')
							$(this).css('position', 'relative');
					});
				}
			});
		} : function() { return this; },
		unfixPNG: IE ? function() {
			return this.each(function () {
				$(this).css({'filter': '', backgroundImage: ''});
			});
		} : function() { return this; },
		hideWhenEmpty: function() {
			return this.each(function() {
				$(this)[ $(this).html() ? "show" : "hide" ]();
			});
		},
		url: function() {
			return this.attr('href') || this.attr('src');
		}
	});
	
	function createHelper(settings) {
		// there can be only one tooltip helper
		if( helper.parent )
			return;
		// create the helper, h3 for title, div for url
		helper.parent = $('<div id="' + settings.id + '"><h3></h3><div class="body"></div><div class="url"></div></div>')
			// add to document
			.appendTo(document.body)
			// hide it at first
			.hide();
			
		// apply bgiframe if available
		if ( $.fn.bgiframe )
			helper.parent.bgiframe();
		
		// save references to title and url elements
		helper.title = $('h3', helper.parent);
		helper.body = $('div.body', helper.parent);
		helper.url = $('div.url', helper.parent);
	}
	
	function settings(element) {
		return $.data(element, "tooltip-settings");
	}
	
	// main event handler to start showing tooltips
	function handle(event) {
		// show helper, either with timeout or on instant
		if( settings(this).delay )
			tID = setTimeout(show, settings(this).delay);
		else
			show();
		
		// if selected, update the helper position when the mouse moves
		track = !!settings(this).track;
		$(document.body).bind('mousemove', update);
			
		// update at least once
		update(event);
	}
	
	// save elements title before the tooltip is displayed
	function save() {
		// if this is the current source, or it has no title (occurs with click event), stop
		if ( $.tooltip.blocked || this == current || (!this.tooltipText && !settings(this).bodyHandler) )
			return;

		// save current
		current = this;
		title = this.tooltipText;
		
		if ( settings(this).bodyHandler ) {
			helper.title.hide();
			var bodyContent = settings(this).bodyHandler.call(this);
			if (bodyContent.nodeType || bodyContent.jquery) {
				helper.body.empty().append(bodyContent)
			} else {
				helper.body.html( bodyContent );
			}
			helper.body.show();
		} else if ( settings(this).showBody ) {
			var parts = title.split(settings(this).showBody);
			helper.title.html(parts.shift()).show();
			helper.body.empty();
			for(var i = 0, part; part = parts[i]; i++) {
				if(i > 0)
					helper.body.append("<br/>");
				helper.body.append(part);
			}
			helper.body.hideWhenEmpty();
		} else {
			helper.title.html(title).show();
			helper.body.hide();
		}
		
		// if element has href or src, add and show it, otherwise hide it
		if( settings(this).showURL && $(this).url() )
			helper.url.html( $(this).url().replace('http://', '') ).show();
		else 
			helper.url.hide();
		
		// add an optional class for this tip
		helper.parent.addClass(settings(this).extraClass);

		// fix PNG background for IE
		if (settings(this).fixPNG )
			helper.parent.fixPNG();
			
		handle.apply(this, arguments);
	}
	
	// delete timeout and show helper
	function show() {
		tID = null;
		helper.parent.show();
		update();
	}
	
	/**
	 * callback for mousemove
	 * updates the helper position
	 * removes itself when no current element
	 */
	function update(event)	{
		if($.tooltip.blocked)
			return;
		
		// stop updating when tracking is disabled and the tooltip is visible
		if ( !track && helper.parent.is(":visible")) {
			$(document.body).unbind('mousemove', update)
		}
		
		// if no current element is available, remove this listener
		if( current == null ) {
			$(document.body).unbind('mousemove', update);
			return;	
		}
		
		// remove position helper classes
		helper.parent.removeClass("viewport-right").removeClass("viewport-bottom");
		
		var left = helper.parent[0].offsetLeft;
		var top = helper.parent[0].offsetTop;
		if(event) {
			// position the helper 15 pixel to bottom right, starting from mouse position
			left = event.pageX + settings(current).left;
			top = event.pageY + settings(current).top;
			helper.parent.css({
				left: left + 'px',
				top: top + 'px'
			});
		}
		
		var v = viewport(),
			h = helper.parent[0];
		// check horizontal position
		if(v.x + v.cx < h.offsetLeft + h.offsetWidth) {
			left -= h.offsetWidth + 20 + settings(current).left;
			helper.parent.css({left: left + 'px'}).addClass("viewport-right");
		}
		// check vertical position
		if(v.y + v.cy < h.offsetTop + h.offsetHeight) {
			top -= h.offsetHeight + 20 + settings(current).top;
			helper.parent.css({top: top + 'px'}).addClass("viewport-bottom");
		}
	}
	
	function viewport() {
		return {
			x: $(window).scrollLeft(),
			y: $(window).scrollTop(),
			cx: $(window).width(),
			cy: $(window).height()
		};
	}
	
	// hide helper and restore added classes and the title
	function hide(event) {
		if($.tooltip.blocked)
			return;
		// clear timeout if possible
		if(tID)
			clearTimeout(tID);
		// no more current element
        current = null;

        helper.parent.hide();

        var s = settings(this);
        
        if (s) {
            if (s.extraClass) {
                helper.parent.removeClass(s.extraClass);
            }

            if (s.fixPNG) {
                helper.parent.unfixPNG();
            }
        }
	}
	
	$.fn.Tooltip = $.fn.tooltip;
	
})(jQuery);
;
/*!
  SerializeJSON jQuery plugin.
  https://github.com/marioizquierdo/jquery.serializeJSON
  version 2.8.1 (Dec, 2016)

  Copyright (c) 2012, 2017 Mario Izquierdo
  Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
  and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
*/
(function (factory) {
  if (typeof define === 'function' && define.amd) { // AMD. Register as an anonymous module.
    define(['jquery'], factory);
  } else if (typeof exports === 'object') { // Node/CommonJS
    var jQuery = require('jquery');
    module.exports = factory(jQuery);
  } else { // Browser globals (zepto supported)
    factory(window.jQuery || window.Zepto || window.$); // Zepto supported on browsers as well
  }

}(function ($) {
  "use strict";

  // jQuery('form').serializeJSON()
  $.fn.serializeJSON = function (options) {
    var f, $form, opts, formAsArray, serializedObject, name, value, parsedValue, _obj, nameWithNoType, type, keys, skipFalsy;
    f = $.serializeJSON;
    $form = this; // NOTE: the set of matched elements is most likely a form, but it could also be a group of inputs
    opts = f.setupOpts(options); // calculate values for options {parseNumbers, parseBoolens, parseNulls, ...} with defaults

    // Use native `serializeArray` function to get an array of {name, value} objects.
    formAsArray = $form.serializeArray();
    f.readCheckboxUncheckedValues(formAsArray, opts, $form); // add objects to the array from unchecked checkboxes if needed

    // Convert the formAsArray into a serializedObject with nested keys
    serializedObject = {};
    $.each(formAsArray, function (i, obj) {
      name  = obj.name; // original input name
      value = obj.value; // input value
      _obj = f.extractTypeAndNameWithNoType(name);
      nameWithNoType = _obj.nameWithNoType; // input name with no type (i.e. "foo:string" => "foo")
      type = _obj.type; // type defined from the input name in :type colon notation
      if (!type) type = f.attrFromInputWithName($form, name, 'data-value-type');
      f.validateType(name, type, opts); // make sure that the type is one of the valid types if defined

      if (type !== 'skip') { // ignore inputs with type 'skip'
        keys = f.splitInputNameIntoKeysArray(nameWithNoType);
        parsedValue = f.parseValue(value, name, type, opts); // convert to string, number, boolean, null or customType

        skipFalsy = !parsedValue && f.shouldSkipFalsy($form, name, nameWithNoType, type, opts); // ignore falsy inputs if specified
        if (!skipFalsy) {
          f.deepSet(serializedObject, keys, parsedValue, opts);
        }
      }
    });
    return serializedObject;
  };

  // Use $.serializeJSON as namespace for the auxiliar functions
  // and to define defaults
  $.serializeJSON = {

    defaultOptions: {
      checkboxUncheckedValue: undefined, // to include that value for unchecked checkboxes (instead of ignoring them)

      parseNumbers: false, // convert values like "1", "-2.33" to 1, -2.33
      parseBooleans: false, // convert "true", "false" to true, false
      parseNulls: false, // convert "null" to null
      parseAll: false, // all of the above
      parseWithFunction: null, // to use custom parser, a function like: function(val){ return parsed_val; }

      skipFalsyValuesForTypes: [], // skip serialization of falsy values for listed value types
      skipFalsyValuesForFields: [], // skip serialization of falsy values for listed field names

      customTypes: {}, // override defaultTypes
      defaultTypes: {
        "string":  function(str) { return String(str); },
        "number":  function(str) { return Number(str); },
        "boolean": function(str) { var falses = ["false", "null", "undefined", "", "0"]; return falses.indexOf(str) === -1; },
        "null":    function(str) { var falses = ["false", "null", "undefined", "", "0"]; return falses.indexOf(str) === -1 ? str : null; },
        "array":   function(str) { return JSON.parse(str); },
        "object":  function(str) { return JSON.parse(str); },
        "auto":    function(str) { return $.serializeJSON.parseValue(str, null, null, {parseNumbers: true, parseBooleans: true, parseNulls: true}); }, // try again with something like "parseAll"
        "skip":    null // skip is a special type that makes it easy to ignore elements
      },

      useIntKeysAsArrayIndex: false // name="foo[2]" value="v" => {foo: [null, null, "v"]}, instead of {foo: ["2": "v"]}
    },

    // Merge option defaults into the options
    setupOpts: function(options) {
      var opt, validOpts, defaultOptions, optWithDefault, parseAll, f;
      f = $.serializeJSON;

      if (options == null) { options = {}; }   // options ||= {}
      defaultOptions = f.defaultOptions || {}; // defaultOptions

      // Make sure that the user didn't misspell an option
      validOpts = ['checkboxUncheckedValue', 'parseNumbers', 'parseBooleans', 'parseNulls', 'parseAll', 'parseWithFunction', 'skipFalsyValuesForTypes', 'skipFalsyValuesForFields', 'customTypes', 'defaultTypes', 'useIntKeysAsArrayIndex']; // re-define because the user may override the defaultOptions
      for (opt in options) {
        if (validOpts.indexOf(opt) === -1) {
          throw new  Error("serializeJSON ERROR: invalid option '" + opt + "'. Please use one of " + validOpts.join(', '));
        }
      }

      // Helper to get the default value for this option if none is specified by the user
      optWithDefault = function(key) { return (options[key] !== false) && (options[key] !== '') && (options[key] || defaultOptions[key]); };

      // Return computed options (opts to be used in the rest of the script)
      parseAll = optWithDefault('parseAll');
      return {
        checkboxUncheckedValue:    optWithDefault('checkboxUncheckedValue'),

        parseNumbers:  parseAll || optWithDefault('parseNumbers'),
        parseBooleans: parseAll || optWithDefault('parseBooleans'),
        parseNulls:    parseAll || optWithDefault('parseNulls'),
        parseWithFunction:         optWithDefault('parseWithFunction'),

        skipFalsyValuesForTypes:   optWithDefault('skipFalsyValuesForTypes'),
        skipFalsyValuesForFields:  optWithDefault('skipFalsyValuesForFields'),
        typeFunctions: $.extend({}, optWithDefault('defaultTypes'), optWithDefault('customTypes')),

        useIntKeysAsArrayIndex: optWithDefault('useIntKeysAsArrayIndex')
      };
    },

    // Given a string, apply the type or the relevant "parse" options, to return the parsed value
    parseValue: function(valStr, inputName, type, opts) {
      var f, parsedVal;
      f = $.serializeJSON;
      parsedVal = valStr; // if no parsing is needed, the returned value will be the same

      if (opts.typeFunctions && type && opts.typeFunctions[type]) { // use a type if available
        parsedVal = opts.typeFunctions[type](valStr);
      } else if (opts.parseNumbers  && f.isNumeric(valStr)) { // auto: number
        parsedVal = Number(valStr);
      } else if (opts.parseBooleans && (valStr === "true" || valStr === "false")) { // auto: boolean
        parsedVal = (valStr === "true");
      } else if (opts.parseNulls    && valStr == "null") { // auto: null
        parsedVal = null;
      }
      if (opts.parseWithFunction && !type) { // custom parse function (apply after previous parsing options, but not if there's a specific type)
        parsedVal = opts.parseWithFunction(parsedVal, inputName);
      }

      return parsedVal;
    },

    isObject:          function(obj) { return obj === Object(obj); }, // is it an Object?
    isUndefined:       function(obj) { return obj === void 0; }, // safe check for undefined values
    isValidArrayIndex: function(val) { return /^[0-9]+$/.test(String(val)); }, // 1,2,3,4 ... are valid array indexes
    isNumeric:         function(obj) { return obj - parseFloat(obj) >= 0; }, // taken from jQuery.isNumeric implementation. Not using jQuery.isNumeric to support old jQuery and Zepto versions

    optionKeys: function(obj) { if (Object.keys) { return Object.keys(obj); } else { var key, keys = []; for(key in obj){ keys.push(key); } return keys;} }, // polyfill Object.keys to get option keys in IE<9


    // Fill the formAsArray object with values for the unchecked checkbox inputs,
    // using the same format as the jquery.serializeArray function.
    // The value of the unchecked values is determined from the opts.checkboxUncheckedValue
    // and/or the data-unchecked-value attribute of the inputs.
    readCheckboxUncheckedValues: function (formAsArray, opts, $form) {
      var selector, $uncheckedCheckboxes, $el, uncheckedValue, f, name;
      if (opts == null) { opts = {}; }
      f = $.serializeJSON;

      selector = 'input[type=checkbox][name]:not(:checked):not([disabled])';
      $uncheckedCheckboxes = $form.find(selector).add($form.filter(selector));
      $uncheckedCheckboxes.each(function (i, el) {
        // Check data attr first, then the option
        $el = $(el);
        uncheckedValue = $el.attr('data-unchecked-value');
        if (uncheckedValue == null) {
          uncheckedValue = opts.checkboxUncheckedValue;
        }

        // If there's an uncheckedValue, push it into the serialized formAsArray
        if (uncheckedValue != null) {
          if (el.name && el.name.indexOf("[][") !== -1) { // identify a non-supported
            throw new Error("serializeJSON ERROR: checkbox unchecked values are not supported on nested arrays of objects like '"+el.name+"'. See https://github.com/marioizquierdo/jquery.serializeJSON/issues/67");
          }
          formAsArray.push({name: el.name, value: uncheckedValue});
        }
      });
    },

    // Returns and object with properties {name_without_type, type} from a given name.
    // The type is null if none specified. Example:
    //   "foo"           =>  {nameWithNoType: "foo",      type:  null}
    //   "foo:boolean"   =>  {nameWithNoType: "foo",      type: "boolean"}
    //   "foo[bar]:null" =>  {nameWithNoType: "foo[bar]", type: "null"}
    extractTypeAndNameWithNoType: function(name) {
      var match;
      if (match = name.match(/(.*):([^:]+)$/)) {
        return {nameWithNoType: match[1], type: match[2]};
      } else {
        return {nameWithNoType: name, type: null};
      }
    },


    // Check if this input should be skipped when it has a falsy value,
    // depending on the options to skip values by name or type, and the data-skip-falsy attribute.
    shouldSkipFalsy: function($form, name, nameWithNoType, type, opts) {
      var f = $.serializeJSON;
      
      var skipFromDataAttr = f.attrFromInputWithName($form, name, 'data-skip-falsy');
      if (skipFromDataAttr != null) {
        return skipFromDataAttr !== 'false'; // any value is true, except if explicitly using 'false' 
      }

      var optForFields = opts.skipFalsyValuesForFields;
      if (optForFields && (optForFields.indexOf(nameWithNoType) !== -1 || optForFields.indexOf(name) !== -1)) {
        return true;
      }
      
      var optForTypes = opts.skipFalsyValuesForTypes;
      if (type == null) type = 'string'; // assume fields with no type are targeted as string
      if (optForTypes && optForTypes.indexOf(type) !== -1) {
        return true
      }

      return false;
    },

    // Finds the first input in $form with this name, and get the given attr from it.
    // Returns undefined if no input or no attribute was found.
    attrFromInputWithName: function($form, name, attrName) {
      var escapedName, selector, $input, attrValue;
      escapedName = name.replace(/(:|\.|\[|\]|\s)/g,'\\$1'); // every non-standard character need to be escaped by \\
      selector = '[name="' + escapedName + '"]';
      $input = $form.find(selector).add($form.filter(selector)); // NOTE: this returns only the first $input element if multiple are matched with the same name (i.e. an "array[]"). So, arrays with different element types specified through the data-value-type attr is not supported.
      return $input.attr(attrName);
    },

    // Raise an error if the type is not recognized.
    validateType: function(name, type, opts) {
      var validTypes, f;
      f = $.serializeJSON;
      validTypes = f.optionKeys(opts ? opts.typeFunctions : f.defaultOptions.defaultTypes);
      if (!type || validTypes.indexOf(type) !== -1) {
        return true;
      } else {
        throw new Error("serializeJSON ERROR: Invalid type " + type + " found in input name '" + name + "', please use one of " + validTypes.join(', '));
      }
    },


    // Split the input name in programatically readable keys.
    // Examples:
    // "foo"              => ['foo']
    // "[foo]"            => ['foo']
    // "foo[inn][bar]"    => ['foo', 'inn', 'bar']
    // "foo[inn[bar]]"    => ['foo', 'inn', 'bar']
    // "foo[inn][arr][0]" => ['foo', 'inn', 'arr', '0']
    // "arr[][val]"       => ['arr', '', 'val']
    splitInputNameIntoKeysArray: function(nameWithNoType) {
      var keys, f;
      f = $.serializeJSON;
      keys = nameWithNoType.split('['); // split string into array
      keys = $.map(keys, function (key) { return key.replace(/\]/g, ''); }); // remove closing brackets
      if (keys[0] === '') { keys.shift(); } // ensure no opening bracket ("[foo][inn]" should be same as "foo[inn]")
      return keys;
    },

    // Set a value in an object or array, using multiple keys to set in a nested object or array:
    //
    // deepSet(obj, ['foo'], v)               // obj['foo'] = v
    // deepSet(obj, ['foo', 'inn'], v)        // obj['foo']['inn'] = v // Create the inner obj['foo'] object, if needed
    // deepSet(obj, ['foo', 'inn', '123'], v) // obj['foo']['arr']['123'] = v //
    //
    // deepSet(obj, ['0'], v)                                   // obj['0'] = v
    // deepSet(arr, ['0'], v, {useIntKeysAsArrayIndex: true})   // arr[0] = v
    // deepSet(arr, [''], v)                                    // arr.push(v)
    // deepSet(obj, ['arr', ''], v)                             // obj['arr'].push(v)
    //
    // arr = [];
    // deepSet(arr, ['', v]          // arr => [v]
    // deepSet(arr, ['', 'foo'], v)  // arr => [v, {foo: v}]
    // deepSet(arr, ['', 'bar'], v)  // arr => [v, {foo: v, bar: v}]
    // deepSet(arr, ['', 'bar'], v)  // arr => [v, {foo: v, bar: v}, {bar: v}]
    //
    deepSet: function (o, keys, value, opts) {
      var key, nextKey, tail, lastIdx, lastVal, f;
      if (opts == null) { opts = {}; }
      f = $.serializeJSON;
      if (f.isUndefined(o)) { throw new Error("ArgumentError: param 'o' expected to be an object or array, found undefined"); }
      if (!keys || keys.length === 0) { throw new Error("ArgumentError: param 'keys' expected to be an array with least one element"); }

      key = keys[0];

      // Only one key, then it's not a deepSet, just assign the value.
      if (keys.length === 1) {
        if (key === '') {
          o.push(value); // '' is used to push values into the array (assume o is an array)
        } else {
          o[key] = value; // other keys can be used as object keys or array indexes
        }

      // With more keys is a deepSet. Apply recursively.
      } else {
        nextKey = keys[1];

        // '' is used to push values into the array,
        // with nextKey, set the value into the same object, in object[nextKey].
        // Covers the case of ['', 'foo'] and ['', 'var'] to push the object {foo, var}, and the case of nested arrays.
        if (key === '') {
          lastIdx = o.length - 1; // asume o is array
          lastVal = o[lastIdx];
          if (f.isObject(lastVal) && (f.isUndefined(lastVal[nextKey]) || keys.length > 2)) { // if nextKey is not present in the last object element, or there are more keys to deep set
            key = lastIdx; // then set the new value in the same object element
          } else {
            key = lastIdx + 1; // otherwise, point to set the next index in the array
          }
        }

        // '' is used to push values into the array "array[]"
        if (nextKey === '') {
          if (f.isUndefined(o[key]) || !$.isArray(o[key])) {
            o[key] = []; // define (or override) as array to push values
          }
        } else {
          if (opts.useIntKeysAsArrayIndex && f.isValidArrayIndex(nextKey)) { // if 1, 2, 3 ... then use an array, where nextKey is the index
            if (f.isUndefined(o[key]) || !$.isArray(o[key])) {
              o[key] = []; // define (or override) as array, to insert values using int keys as array indexes
            }
          } else { // for anything else, use an object, where nextKey is going to be the attribute name
            if (f.isUndefined(o[key]) || !f.isObject(o[key])) {
              o[key] = {}; // define (or override) as object, to set nested properties
            }
          }
        }

        // Recursively set the inner object
        tail = keys.slice(1);
        f.deepSet(o[key], tail, value, opts);
      }
    }

  };

}));
;
// https://datatables.net/plug-ins/sorting/currency

$(function () {
    jQuery.extend(jQuery.fn.dataTableExt.oSort, {
        "currency-pre": function (a) {
            a = (a === "-") ? 0 : a.replace(/[^\d\-\.]/g, "");
            return parseFloat(a);
        },

        "currency-asc": function (a, b) {
            return a - b;
        },

        "currency-desc": function (a, b) {
            return b - a;
        }
    });
});;
window.EdelweissAnalytics = window.EdelweissAnalytics || {};
EdelweissAnalytics.rows = [];
EdelweissAnalytics.selected = 0;
EdelweissAnalytics.weedingAnalysisRecords = [];
EdelweissAnalytics.categoryPerformanceComparisonRecords = [];
EdelweissAnalytics.numberOfSuggestionsByWeedingType = {};
EdelweissAnalytics.selectedWeedingType = "all";

var oTable = null;
var cTable = null;
var viewType = "overstockView";
EdelweissAnalytics.isViewingDetailsGrid = false;
EdelweissAnalytics.categoryTypeForComparison = 0;
EdelweissAnalytics.rootCategoryValueForComparison = {};
EdelweissAnalytics.rootCategoryValueForComparison.categoryCode = "";
EdelweissAnalytics.rootCategoryValueForComparison.categoryName = "";
EdelweissAnalytics.MaximumNumberOfTitlesForExport = 20000;
EdelweissAnalytics.hasSelectAllTitlesOnAllPagesTriggered = false;
EdelweissAnalytics.laneUpdateProcessAbortSilently = "abortWaterfall";
EdelweissAnalytics.isViewingIndividualLane = false;

EdelweissAnalytics.segmentationModes = {
    Unknown: 0,
    Activity: 1,
    Turn: 2
}

EdelweissAnalytics.colors = {};
EdelweissAnalytics.colors.red = "#FC601F";
EdelweissAnalytics.colors.orange = "#FFC321";
EdelweissAnalytics.colors.yellow = "#FFEA00";
EdelweissAnalytics.colors.greenNeon = "#CCFF66";
EdelweissAnalytics.colors.greenLight = "#92D050";
EdelweissAnalytics.colors.greenDark = "#598A18";
EdelweissAnalytics.colors.blueLight = "#529BD4";
EdelweissAnalytics.colors.blueDark = "#2767B8";
EdelweissAnalytics.colors.blueGreen = "#72B692";

EdelweissAnalytics.rowSelectedClass = "isSelectedDataTableRow";

EdelweissAnalytics.customCategoryTypes = ["410", "11000", "11001", "11002", "11003", "11004", "11005", "420", "430", "1414"];

EdelweissAnalytics.euroCultureCodes = ["en-GB", "fr"];

var FilterOption = function () {
    this.attributeFilters = [];
    this.searchName = "";
    this.pubDateFilter = ""; //Time range expressed in Solr query syntax.
    this.monthsBack = 3;
    this.month = 0;
    this.year = 0;
    this.numberRequested = 10;
    this.locationFilters = ["All"];
    this.catalogFilters = [];
    this.catalogName = "";
    this.productIds = [];
    this.familyKeys = [];
    this.excludedProductIds = [];
    this.marketFilters = [];
    this.version = 1;
    this.includeTitleData = false;
    this.org = "";
    this.user = "";
    this.stockAnalysisClass = 0;
    this.includeNotInStockAndNotOnOrder = false;
    this.doSolrServerPaging = false;
    this.restrictNotInStockAndNotOnOrderByMinimumLastSoldDate = false;
    this.includeTitlesFromAllStockAnalysisClasses = false;
    this.wedgeColors = [];
    this.referenceCode = "";
    this.inventoryIndexRangeLower = 60;
    this.inventoryIndexRangeUpper = 270;
    this.sortColumnName = "";
    this.sortDirection = "";
    this.peerOrgId = "";
    this.peerBranchId = "";
    this.analysisCacheKey = "";
    this.filtersCacheKey = "";
    this.minimumCopiesOnHand = 0;
    this.minimumLastSoldDate = "";
    this.excludedProductIdsType = 0;
    this.segmentationMode = EdelweissAnalytics.segmentationModes.Unknown;
    this.includeNeverStockedSegment = false;
    this.turnRangeLower = 1.0;
    this.turnRangeUpper = 3.0;
    this.canSelectFromMultiSegmentationModes = false;
}

FilterOption.prototype.addToMarketFilters = function (marketId) {
    this.marketFilters = [];
    this.marketFilters.push(marketId);
};

FilterOption.prototype.addToCatalogFilters = function (catalogId) {
    this.catalogFilters = [];
    if (catalogId > 0) {
        this.catalogFilters.push(catalogId);
    } else {
        this.catalogFilters.push(0);
    }
}

FilterOption.prototype.addToLocationFilters = function (locationId) {
    this.locationFilters = [];
    this.locationFilters.push(locationId);
}

EdelweissAnalytics.filterOptions = {};
EdelweissAnalytics.isFirstTimeLaneLoad = {};
EdelweissAnalytics.openedDetailsGrid = {};
EdelweissAnalytics.isVisibleLane = {};
EdelweissAnalytics.reLoadAnalyticsDetail = {};
EdelweissAnalytics.stockAnalysisClasses = {};
EdelweissAnalytics.analysisCacheKey = "";
EdelweissAnalytics.stockAnalysisClass = 0;
EdelweissAnalytics.isAnalyticsUser = false;
EdelweissAnalytics.titleGridPageLength = 10;
EdelweissAnalytics.laneSettingChange = {};
/*
    If a sort other than the default for a lane/wedge is chosen, store it in these two data structures.

    When the lane is updated, apply the values in these two data structures 
    to filterOptions.sortDirection and filterOptions.sortColumnName only if they exist,
    which meant a sort other than the default was used for the current wedge.

    When filterOptions.sortDirection and filterOptions.sortColumnName are specified, they
    override the sort used by default on the server-side.
*/
EdelweissAnalytics.sortColumnNameByStockAnalysisClass = {};
EdelweissAnalytics.sortDirectionByStockAnalysisClass = {};
EdelweissAnalytics.sortColumnNameForAllTitles = {};
EdelweissAnalytics.sortDirectionForAllTitles = {};

EdelweissAnalytics.LaneKeys = {
    Unknown: "Unknown",
    MostPopularTitles: "MostPopularTitles",
    NotYetReleasedTitles: "NotYetReleasedTitles",
    CatalogAnalysis: "CatalogAnalysis",
    StockAnalysis: "StockAnalysis",
    PeerBranchStockAnalysis: "PeerBranchStockAnalysis",
    CategoryPerformanceAnalysis: "CategoryPerformanceAnalysis",
    LocationPerformanceAnalysis: "LocationPerformanceAnalysis",
    TrendsAnalysis: "TrendsAnalysis",
    TitleSetStockAnalysis: "TitleSetStockAnalysis",
    WeedingAnalysis: "WeedingAnalysis",
    RestockSupport: "RestockSupport"
};

EdelweissAnalytics.DataSourceKeys = {
    TitleData: "TitleData",
    CategoryPerformanceData: "CategoryPerformanceData",
    LocationPerformanceData: "LocationPerformanceData",
    TrendsAnalysisData: "TrendsAnalysisData",
    WeedingAnalysisData: "WeedingAnalysisData"
};

EdelweissAnalytics.trendsAnalysisGraphType = {
    circulation: "CirculationTrendAnalysis",
    inventory: "InventoryTrendAnalysis",
    turn: "TurnTrendAnalysis",
    revenues: "RevenuesTrendAnalysis",
    shareOfTotalStore: "ShareOfTotalStore"
};

EdelweissAnalytics.stockAnalysisClassNames = {
    0: "Not Owned",
    1: "Not Owned, Ordered",
    2: "Owned, Fresh/Hot",
    3: "Owned, Stable",
    4: "Owned, Stale/Cool"
};

EdelweissAnalytics.StockAnalysisClassUsingActivityIndex = {
    OutOfStock: 0,
    OutOfStockOnOrder: 1,
    Freshest: 2,
    Fresher: 3,
    Stale: 4
};

EdelweissAnalytics.listTypes = {
    Analytics_ISBNView: 11,
    Analytics_FamilyView: 12
};

EdelweissAnalytics.referenceCodes = {
    IsbnView: "IsbnView",
    FamilyView: "FamilyView",
    FamilyDetailsView: "FamilyDetailsView",
    FamilyDetailsViewInSeriesView: "FamilyDetailsViewInSeriesView"
}

EdelweissAnalytics.listType = {
    201: EdelweissAnalytics.listTypes.Analytics_ISBNView,
    205: EdelweissAnalytics.listTypes.Analytics_ISBNView
};

EdelweissAnalytics.weedingType = {
    all: "all",
    poorCirculation: "poorcirculation",
    outdated: "outdated",
    worn: "worn"
};

EdelweissAnalytics.chartType = {
    barChart: "BarChart",
    lineChart: "LineChart",
    pieChart: "PieChart",
    heatMapChart: "HeatMapChart"
};

EdelweissAnalytics.valueFormat = {
    currency: "Currency",
    percent: "Percent",
    number: "Number"
};

EdelweissAnalytics.stockAnalysisClassUsingTurn = {
    neverStocked: 0,
    noSales: 1,
    lowTurn: 2,
    mediumTurn: 3,
    highTurn: 4
};

EdelweissAnalytics.stockAnalysisWedgeColorsBySegmentationMode = {};

EdelweissAnalytics.stockAnalysisWedgeColorsBySegmentationMode[EdelweissAnalytics.segmentationModes.Activity] = [
    EdelweissAnalytics.colors.red,
    EdelweissAnalytics.colors.yellow,
    EdelweissAnalytics.colors.greenLight,
    EdelweissAnalytics.colors.blueGreen,
    EdelweissAnalytics.colors.blueLight
];

EdelweissAnalytics.stockAnalysisWedgeColorsBySegmentationMode[EdelweissAnalytics.segmentationModes.Turn] = [
    EdelweissAnalytics.colors.red,
    EdelweissAnalytics.colors.orange,
    EdelweissAnalytics.colors.blueLight,
    EdelweissAnalytics.colors.blueGreen,
    EdelweissAnalytics.colors.greenLight
];

EdelweissAnalytics.temporaryCategoryFilterPath = [];
EdelweissAnalytics.categoryNameForComparison = "";

var dashTypesOfAnalyticsLanes = [101, 102, 103, 201, 204, 205, 206, 207, 208, 210, 220, 221, 222, 225, 230, 231];
dashTypesOfAnalyticsLanes.forEach(function (dashType) {
    EdelweissAnalytics.filterOptions[dashType] = new FilterOption();
    EdelweissAnalytics.sortColumnNameByStockAnalysisClass[dashType] = {};
    EdelweissAnalytics.sortDirectionByStockAnalysisClass[dashType] = {};
    EdelweissAnalytics.sortColumnNameForAllTitles[dashType] = "";
    EdelweissAnalytics.sortDirectionForAllTitles[dashType] = "";
    EdelweissAnalytics.isFirstTimeLaneLoad[dashType] = true;
    EdelweissAnalytics.openedDetailsGrid[dashType] = false;
    EdelweissAnalytics.isVisibleLane[dashType] = false;
    EdelweissAnalytics.reLoadAnalyticsDetail[dashType] = false;
    EdelweissAnalytics.laneSettingChange[dashType] = false;
});

EdelweissAnalytics.receivableOptions = {
    primaryRegion: "",
    clientType: ""
};
EdelweissAnalytics.displayOptions = {
    loadingText: "Loading",
    maxListView: 1000
};
EdelweissAnalytics.marketDataBySkuCache = {};
EdelweissAnalytics.dashTypeSkuCache = {};
EdelweissAnalytics.analysisCacheKey = "";
EdelweissAnalytics.stockAnalysisClass = 0;

EdelweissAnalytics.isTrendsAnalysisChartUpdated = false;
EdelweissAnalytics.isTrendsAnalysisDetailsGridUpdated = false;

EdelweissAnalytics.clearPubDateFilter = function (dashType) {
    EdelweissAnalytics.filterOptions[dashType].pubDateFilter = "";
}

EdelweissAnalytics.getRefinementAttributeFilters = function (dashType) {
    var refinementAttributeFilters = _.filter(EdelweissAnalytics.filterOptions[dashType].attributeFilters,
        function (attributeFilter) {
            return attributeFilter.isRefinement;
        });
    return refinementAttributeFilters;
};

EdelweissAnalytics.clearContentFilters = function (dashType, doKeepRefinementFilters) {
    EdelweissAnalytics.clearPubDateFilter(dashType);
    if (Array.isArray(EdelweissAnalytics.filterOptions[dashType].attributeFilters)) {
        if (doKeepRefinementFilters) {
            EdelweissAnalytics.filterOptions[dashType].attributeFilters =
                EdelweissAnalytics.getRefinementAttributeFilters(dashType);
        } else {
            EdelweissAnalytics.filterOptions[dashType].attributeFilters.length = 0;
        }
    }
}

EdelweissAnalytics.addPubDateFilter = function (dashType, filter) {
    EdelweissAnalytics.filterOptions[dashType].pubDateFilter = filter;
}

EdelweissAnalytics.getIndexOfRefinementAttributeFilterWithFilterType = function (attributeFilters, filterType) {
    var attributeFilterIndex = _.findIndex(attributeFilters, function (filter) {
        return filter.isRefinement && filter.attributeName == filterType;
    });
    return attributeFilterIndex;
};

EdelweissAnalytics.addRefinementToAttributeFilters = function (dashType, filterType, refinement) {
    if (!window.EdelweissAnalytics.filterOptions.hasOwnProperty(dashType)) return;
    var attributeFilters = window.EdelweissAnalytics.filterOptions[dashType].attributeFilters;
    var attributeFilterIndex = window.EdelweissAnalytics.getIndexOfRefinementAttributeFilterWithFilterType(
        attributeFilters, filterType);
    if (attributeFilterIndex > -1) {
        var filterValues = attributeFilters[attributeFilterIndex].filterValues;
        if (!_.includes(filterValues, refinement)) {
            filterValues.push(refinement);
        }
    } else {
        var attributeFilter = {
            attributeName: filterType,
            filterValues: [refinement],
            isRefinement: true
        };
        attributeFilters.push(attributeFilter);
    }
};

EdelweissAnalytics.removeRefinementFromAttributeFilters = function (dashType, filterType, refinement) {
    if (!window.EdelweissAnalytics.filterOptions.hasOwnProperty(dashType)) return;
    var attributeFilters = window.EdelweissAnalytics.filterOptions[dashType].attributeFilters;
    var attributeFilterIndex = window.EdelweissAnalytics.getIndexOfRefinementAttributeFilterWithFilterType(
        attributeFilters, filterType);
    if (attributeFilterIndex > -1) {
        var filterValues = attributeFilters[attributeFilterIndex].filterValues;
        var filterValueIndex = filterValues.indexOf(refinement);
        if (filterValueIndex > -1) {
            filterValues.splice(filterValueIndex, 1);
            if (filterValues.length === 0) {
                attributeFilters.splice(attributeFilterIndex, 1);
            }
        }
    }
};

EdelweissAnalytics.updateAttributeFiltersWithRefinement = function (dashType, filterType, refinement, doAdd) {
    if (doAdd) {
        EdelweissAnalytics.addRefinementToAttributeFilters(dashType, filterType, refinement);
    } else {
        EdelweissAnalytics.removeRefinementFromAttributeFilters(dashType, filterType, refinement);
    }
};

EdelweissAnalytics.addToAttributeFilters = function (dashType, attributeName, filterValue, attributeId, custom, not) {
    if (typeof attributeId === "undefined" || attributeId === null) {
        attributeId = 0;
    }
    if (typeof custom === "undefined" || custom === null) {
        custom = false;
    }
    if (typeof not === "undefined" || not === null) {
        not = false;
    }

    var attributeFilter = {
        attributeName: attributeName,
        filterValues: [filterValue],
        attributeId: attributeId,
        custom: custom,
        not: not
    };

    var addit = true;
    for (var i = 0; i < EdelweissAnalytics.filterOptions[dashType].attributeFilters.length; i++) {
        var currentFilter = EdelweissAnalytics.filterOptions[dashType].attributeFilters[i];
        if (currentFilter.attributeName === attributeFilter.attributeName) {
            addit = false;
            break;
        }
    }

    if (addit) {
        EdelweissAnalytics.filterOptions[dashType].attributeFilters.push(attributeFilter);
    }
}

EdelweissAnalytics.removeAttributeFilterFromFilterOptions = function (dashType, attributeName) {
    var index = -1;
    if (Array.isArray(EdelweissAnalytics.filterOptions[dashType].attributeFilters)) {
        index = arrayObjectIndexOf(EdelweissAnalytics.filterOptions[dashType].attributeFilters, attributeName, "attributeName");
        if (index > -1) {
            EdelweissAnalytics.filterOptions[dashType].attributeFilters.splice(index, 1);
        }
    }
}

EdelweissAnalytics.isCustomCategoryId = function (browseByTypeId) {
    return _.includes(EdelweissAnalytics.customCategoryTypes, browseByTypeId);
}

EdelweissAnalytics.isCustomCategoryInAttributeFilters = function (dashType) {
    var attributeFilters = EdelweissAnalytics.filterOptions[dashType].attributeFilters;
    var isCustomCategoryInAttributeFilters = attributeFilters.some(function (attributeFilter) {
        var isCustomCategoryAttributeFilter = false;
        if (attributeFilter.isRefinement) {
            isCustomCategoryAttributeFilter = attributeFilter.attributeName == getEnumValue("filterType", "POSCATEGORY");
        } else {
            // attributeName comes in the format refine_XXXX_YYYY
            var attributeName = attributeFilter.attributeName;
            var attributeNameParts = attributeName.split("_");
            // Make sure the split resulted in an array of at least 2 items
            if (Array.isArray(attributeNameParts) && attributeNameParts.length > 1) {
                var browseByTypeId = attributeNameParts[1];
                isCustomCategoryAttributeFilter = EdelweissAnalytics.isCustomCategoryId(browseByTypeId);
            }
        }
        if (isCustomCategoryAttributeFilter) {
            return true;
        }
    });
    return isCustomCategoryInAttributeFilters;
}

EdelweissAnalytics.addToLocationFilters = function (dashType, locationId) {
    //setup to support multiple location selection in the future.
    EdelweissAnalytics.filterOptions[dashType].locationFilters = [];
    EdelweissAnalytics.filterOptions[dashType].locationFilters.push(locationId);
}
EdelweissAnalytics.addToCatalogFilters = function (dashType, catalogId) {
    //setup to support multiple catalog selection in the future.
    EdelweissAnalytics.filterOptions[dashType].catalogFilters = [];
    if (catalogId > 0) {
        EdelweissAnalytics.filterOptions[dashType].catalogFilters.push(catalogId);
    } else {
        EdelweissAnalytics.filterOptions[dashType].catalogFilters.push(0);
    }
}
EdelweissAnalytics.addToMarketFilters = function (dashType, marketId) {
    //setup to support multiple market selection in the future.
    EdelweissAnalytics.filterOptions[dashType].marketFilters = [];
    EdelweissAnalytics.filterOptions[dashType].marketFilters.push(marketId);
}
EdelweissAnalytics.addToMarketDataBySkuCache = function (dashType, marketDataBySku) {
    for (var i = 0; i < marketDataBySku.length; i++) {
        EdelweissAnalytics.marketDataBySkuCache[EdelweissAnalytics.filterOptions[dashType].marketFilters[0] + "|" + marketDataBySku[i].sku] = marketDataBySku[i];
    }
}

EdelweissAnalytics.getMarketDataForASkuFromCache = function (dashType, sku) {
    return EdelweissAnalytics.marketDataBySkuCache[EdelweissAnalytics.filterOptions[dashType].marketFilters[0] + "|" + sku];
}

EdelweissAnalytics.getPtoFromMarketDataCache = function (dashType, sku) {
    return EdelweissAnalytics.marketDataBySkuCache[EdelweissAnalytics.filterOptions[dashType].marketFilters[0] + "|" + sku].pto;
}
EdelweissAnalytics.getPcFromMarketDataCache = function (dashType, sku) {
    return EdelweissAnalytics.marketDataBySkuCache[EdelweissAnalytics.filterOptions[dashType].marketFilters[0] + "|" + sku].pc;
}
EdelweissAnalytics.getUnitsFromMarketDataCache = function (dashType, sku) {
    return EdelweissAnalytics.marketDataBySkuCache[EdelweissAnalytics.filterOptions[dashType].marketFilters[0] + "|" + sku].units;
}

EdelweissAnalytics.updateSalesPipeline = function (dashType, resultType) {
    var showPipelineList = $("#pipelineList").length;

    window.savingModalControl(EdelweissAnalytics.displayOptions.loadingText, "dashLoad_" + dashType);
    var values = {
        orgID: EdelweissAnalytics.filterOptions[dashType].org,
        user: EdelweissAnalytics.filterOptions[dashType].user
    };
    $.getJSON("/GetJSONData.aspx?m=Analytics&builder=GetSalesPipelineData", values, function (data) {
        EdelweissAnalytics.updateChartHeadersFooters(dashType, resultType, data.text, data.code + " To Contact", '')
        var jsonData = data.data;
        var colors = ['#92D050', '#CCFF66', '#FFEA00', '#fc601f'];
        window.drawStandardPie(dashType, jsonData, colors)
        if (showTitleDetail) {
            //showAnalyticsDetail(dashType)
            populateAnalyticPipelineScroll(resultType, dashType)
        }
    });
}
EdelweissAnalytics.updateSalesReceivables = function (dashType, resultType) {
    var showDetail = $("#detail_" + dashType).length;
    google.charts.load('current', { packages: ['table'] });
    window.savingModalControl(EdelweissAnalytics.displayOptions.loadingText, "dashLoad_" + dashType);
    var values = {
        primaryRegion: EdelweissAnalytics.receivableOptions.primaryRegion,
        clientType: EdelweissAnalytics.receivableOptions.clientType
    };
    $.getJSON("/GetJSONData.aspx?m=Analytics&builder=GetSalesReceivables", values, function (data) {
        EdelweissAnalytics.updateChartHeadersFooters(dashType, resultType, data.text, data.code + " Late A/R", '')
        var jsonData = data.data;
        var colors = ['#92D050', '#CCFF66', '#FFEA00', '#fc601f'];
        window.drawStandardPie(dashType, jsonData, colors);

        var cssClassNames = {
            tableRow: 'overallStyles',
            oddTableRow: 'overallStyles',
            headerRow: 'hidden'
        }
        var data = new google.visualization.DataTable(jsonData);
        var table = new google.visualization.Table(document.getElementById('receivablesTable'));
        table.draw(data, { showRowNumber: false, width: '100%', height: '100%', 'cssClassNames': cssClassNames });
    });
}
EdelweissAnalytics.getSalesFolks = function (dashType) {
    var values = {
        orgID: EdelweissAnalytics.filterOptions[dashType].org
    };
    $("#userOptions").html("")
    $.getJSON("/GetJSONData.aspx?m=Analytics&builder=GetSalesFolks", values, function (data) {
        if (data && data.length) {
            var dHtml = "<table>"
            var colCount = 0
            for (var i = 0; i < data.length; i++) {
                if (colCount === 0) {
                    dHtml += "<tr>"
                }
                var checked = "box_unchecked"
                if (EdelweissAnalytics.filterOptions[dashType].user === data[i].code) {
                    checked = "box_checked"
                }
                dHtml += "<td><div class='columnSpaced " + checked + " userOption' id='user_" + i + "' data-user='" + data[i].code + "'></div>"
                dHtml += "<div class='columnSpaced'>" + data[i].text + "</div><div style='clear:both;'></td>"
                if (colCount === 2 || i + 1 === data.length) {
                    dHtml += "</tr>"
                    colCount = 0
                } else {
                    colCount += 1
                }
            }
            dHtml += "</table>"
            $("#userOptions").html(dHtml)
            $('.userOption').click(function () {
                window.enableMultipleCheckbox($(this), "userOption");
                window.saveSalesPersonPreference($(this).attr("data-user"));
            });
        }
    });
}

function showLoadingDiv(laneKey, dashType) {
    var targetId = getLoadingDivTargetId(laneKey, dashType);
    if (targetId !== null) {
        window.savingModalControlGeneric(EdelweissAnalytics.displayOptions.loadingText, targetId.replace("#", ""));
        $(targetId).show();
    }
}

function closeLoadingDiv(laneKey, dashType) {
    var targetId = getLoadingDivTargetId(laneKey, dashType);
    if (targetId !== null) {
        window.closeSavingModalControl(targetId.replace("#", ""));
        $(targetId).hide();
    }
}

function getLoadingDivTargetId(laneKey, dashType) {
    // a null target means one was not found 
    // (i.e. this function was called from a page that was not Edelweiss Home or Analytics Home)
    target = null;
    if (IsOnEdelweissHome()) {
        target = "#chartLoad_" + laneKey + "_0";
    } else if (IsOnAnalyticsHome(dashType)) {
        if (EdelweissAnalytics.hasSelectAllTitlesOnAllPagesTriggered) {
            target = "#dashDetailLoad_" + dashType;
        }
        else {
            target = "#dashLoad_" + dashType;
        }
    }
    return target;
}

EdelweissAnalytics.refreshFiltersStatus = function (lane) {
    var filtersStatus = "";

    if (lane.isPreferencesDialogBoxIncluded && lane.preferencesDialogBox.isPubDateRangeIncluded) {
        if (!_.isEmpty(lane.search.pubDateLowerBoundString) && !_.isEmpty(lane.search.pubDateUpperBoundString)) {
            if (lane.search.pubDateUpperBoundString === "*") {
                var pubDateRangeStatus = new Date(lane.search.pubDateLowerBoundString).toLocaleDateString(ePlus.user.culture)
                    + ' ' + getRes('and_later');
            } else {
                var pubDateRangeStatus = new Date(lane.search.pubDateLowerBoundString).toLocaleDateString(ePlus.user.culture)
                    + " to " + new Date(lane.search.pubDateUpperBoundString).toLocaleDateString(ePlus.user.culture);
            }
            filtersStatus += getRes("pub_date") + ": " + pubDateRangeStatus;
        }
    }

    if (lane.isPreferencesDialogBoxIncluded && lane.preferencesDialogBox.isMinimumCopiesOnHandIncluded) {
        if (lane.search.minimumCopiesOnHand > 0) {
            if (filtersStatus) {
                filtersStatus += "<br>";
            }
            var copyLabel = lane.search.minimumCopiesOnHand === 1 ? "copy" : "copies";
            filtersStatus += "At least " + lane.search.minimumCopiesOnHand + " " + copyLabel + " on hand";
        }
    }

    if (filtersStatus) {
        $("#filtersStatusContainer_" + lane.key).show();
        $("#filtersStatus_" + lane.key).html(filtersStatus);
    } else {
        $("#filtersStatusContainer_" + lane.key).hide();
    }
}

EdelweissAnalytics.refreshDataDescription = function (lane) {
    var dataDescription = lane.header.dataDescription;
    if (lane.key === EdelweissAnalytics.LaneKeys.CategoryPerformanceAnalysis) {
        EdelweissAnalytics.categoryNameForComparison = lane.header.dataDescription;
        if (EdelweissAnalytics.rootCategoryValueForComparison.categoryName !== "") {
            dataDescription += " " + EdelweissAnalytics.rootCategoryValueForComparison.categoryName;
        }
    }
    $("#dash_ResultHeader_" + lane.resultType).html(dataDescription);
}

function isProductIdsListValidWhenCollected(lane) {
    var productIds = [];
    if (Array.isArray(EdelweissAnalytics.filterOptions[lane.dashType].productIds)) {
        productIds = productIds.concat(EdelweissAnalytics.filterOptions[lane.dashType].productIds);
    }
    if (Array.isArray(EdelweissAnalytics.filterOptions[lane.dashType].familyKeys)) {
        productIds = productIds.concat(EdelweissAnalytics.filterOptions[lane.dashType].familyKeys);
    }
    return !isProductIdsListCollected(lane) || productIds.length > 0;
}

function isProductIdsListCollected(lane) {
    return lane.key === EdelweissAnalytics.LaneKeys.TitleSetStockAnalysis
        || (lane.isSearchIncluded
            && lane.search.productIdsSource !== null
            && lane.search.productIdsSource.length > 0);
}

// This is the entry point for updating a lane
// It can be called from any page, and based on what elements are on the page,
// it will update those with the lane's data
EdelweissAnalytics.startLaneUpdateProcess = function (laneKey) {
    var doneUpdatingLanePromise = new Promise(function (resolve, reject) {
        $.get('/api/v2/analytics/lanes/' + laneKey, function (lane) {
            if (EdelweissAnalytics.isFirstTimeLaneLoad[lane.dashType]) {
                SetFilterOptionsOnFirstTimeLaneLoad(lane);
            }
            SetFilterOptionsForEveryLaneLoad(lane);

            var laneUpdateProcesses = [];
            laneUpdateProcesses.push(
                async.apply(LoadInitialComponents, lane)
            );
            laneUpdateProcesses.push(
                async.apply(RefreshLaneElementsWithInitialLaneData, lane)
            );
            if (!lane.isSearchIncluded || !shouldSearchProceed(lane)) {
                laneUpdateProcesses.push(GetNullData);
            } else {
                laneUpdateProcesses.push(
                    async.apply(GetProductIds, lane),
                    async.apply(GetData, lane)
                );
            }
            laneUpdateProcesses.push(
                async.apply(UpdateLaneComponents, lane)
            );
            async.waterfall(laneUpdateProcesses, function (err, result) {
                if (err && err.message != EdelweissAnalytics.laneUpdateProcessAbortSilently) {
                    console.warn(lane.key + ": " + err);
                }
                resetLaneStateKeepers(lane.key, lane.dashType);
                closeLoadingDiv(lane.key, lane.dashType);
                resolve();
            });
        });
    });
    return doneUpdatingLanePromise;
};

function GetNullData(callback) {
    var data = null;
    return callback(null, data);
}

function resetLaneStateKeepers(laneKey, dashType) {
    EdelweissAnalytics.isFirstTimeLaneLoad[dashType] = false;
    EdelweissAnalytics.openedDetailsGrid[dashType] = false;
    EdelweissAnalytics.reLoadAnalyticsDetail[dashType] = false;
    if (laneKey === EdelweissAnalytics.LaneKeys.TrendsAnalysis) {
        EdelweissAnalytics.isTrendsAnalysisChartUpdated = false;
        EdelweissAnalytics.isTrendsAnalysisDetailsGridUpdated = false;
    }
}

function LoadInitialComponents(lane, callback) {
    var loadComponentFunctions = [];
    if (lane.isChartIncluded && hasChartOnPage(lane.dashType)
        && (EdelweissAnalytics.isFirstTimeLaneLoad[lane.dashType] ||
            IsOnEdelweissHome())) {
        loadComponentFunctions.push(
            async.apply(LoadInitialCharts, lane));
    }
    if (lane.isDetailsGridIncluded && EdelweissAnalytics.isViewingDetailsGrid
        && LaneIsVisible(lane.dashType)
        && UpdateDetailsGridIfTrendsAnalysisLane(lane.key)
        && (EdelweissAnalytics.isFirstTimeLaneLoad[lane.dashType] ||
            EdelweissAnalytics.openedDetailsGrid[lane.dashType] ||
            EdelweissAnalytics.reLoadAnalyticsDetail[lane.dashType])) {
        loadComponentFunctions.push(
            async.apply(LoadInitialDetailsGrid, lane));
    }
    async.parallel(loadComponentFunctions, function (err, results) {
        if (err) {
            return callback(err);
        } else {
            showLoadingDiv(lane.key, lane.dashType);
            return callback(null);
        }
    });
}

function getChartsToProcess(lane) {
    var chartsToLoad = lane.charts;
    // Home page only shows one chart
    if (IsOnEdelweissHome() && lane.charts && lane.charts.length > 1) {
        chartsToLoad = [lane.charts[0]];
    }
    return chartsToLoad;
}

function LoadInitialCharts(lane, callback) {
    var loadChartFunctions = [];
    var chartsToLoad = getChartsToProcess(lane);
    for (i = 0; i < chartsToLoad.length; i++) {
        loadChartFunctions.push(
            async.apply(LoadInitialChart, lane, i));
    }
    async.parallel(loadChartFunctions, function (err, results) {
        if (err) {
            return callback(err);
        } else {
            return callback(null);
        }
    });
}

function LoadInitialChart(lane, chartIndex, callback) {
    if (lane.key === EdelweissAnalytics.LaneKeys.WeedingAnalysis
        && lane.charts[chartIndex].type === EdelweissAnalytics.chartType.barChart) {
        // no initial component to load
        return callback(null);
    }

    var chartElement = $("#chart_" + lane.dashType + "_" + chartIndex);
    if (chartElement.length > 0) {
        var analyticsChartParams = {
            laneKey: lane.key,
            chartIndex: chartIndex,
            chartType: lane.charts[chartIndex].type
        }
        $.url = "/GetTreelineControl.aspx?controlName=/uc/analytics/dashContent/AnalyticsChart.ascx&" + $.param(analyticsChartParams);
        chartElement.load($.url, function (response, status, xhr) {
            if (status === "error") {
                return callback(new Error("Error: " + xhr.status + " " + xhr.statusText));
            } else {
                return callback(null, "Successful chart load");
            }
        });
    } else {
        return callback(new Error(EdelweissAnalytics.laneUpdateProcessAbortSilently));
    }
}

function LoadInitialDetailsGrid(lane, callback) {
    $.url = "/GetTreelineControl.aspx?controlName=/uc/analytics/dashDetails/AnalyticsDetail.ascx"
        + "&laneKey=" + lane.key;
    $("#analytics_content").load($.url, function (response, status, xhr) {
        if (status === "error") {
            return callback(new Error("Error: " + xhr.status + " " + xhr.statusText));
        } else {
            $("#analytics_content").show();
            return callback(null, "Successful details grid load");
        }
    });
}

function RefreshLaneElementsWithInitialLaneData(lane, callback) {
    if (lane.header.isDataDescriptionIncluded && hasDataDescriptionOnPage(lane.resultType)) {
        EdelweissAnalytics.refreshDataDescription(lane);
    }
    if (lane.isFiltersStatusIncluded && hasFiltersStatusOnPage(lane.key)) {
        EdelweissAnalytics.refreshFiltersStatus(lane);
    }
    return callback(null);
}

EdelweissAnalytics.collapseLane = function (dashType, laneSizeCssClass) {
    $("#dash_" + dashType).removeClass(laneSizeCssClass);
};

EdelweissAnalytics.uncollapseLane = function (dashType, laneSizeCssClass) {
    $("#dash_" + dashType).addClass(laneSizeCssClass);
};

function UpdateLaneComponents(lane, data, callback) {
    if (lane.isSearchIncluded) {
        if (isLaneCollapsed(lane, data)) {
            updateStatusMessageAsLaneIsCollapsed(lane, data);
            EdelweissAnalytics.collapseLane(lane.dashType, lane.laneSizeCssClass);
        } else {
            clearAnalyticsLaneStatusMessage(lane.key);
            EdelweissAnalytics.uncollapseLane(lane.dashType, lane.laneSizeCssClass);

            if (lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.TitleData) {
                var actualStockAnalysisClass = parseInt(data.stockAnalysisClass);
                if (actualStockAnalysisClass !== EdelweissAnalytics.filterOptions[lane.dashType].stockAnalysisClass) {
                    EdelweissAnalytics.filterOptions[lane.dashType].stockAnalysisClass = actualStockAnalysisClass;
                }
            }

        }
    }

    var componentUpdateFunctions = [];
    if (lane.isChartSummaryIncluded && IsOnAnalyticsHome(lane.dashType)) {
        componentUpdateFunctions.push(
            async.apply(UpdateChartSummary, data, lane));
    }
    if (lane.isChartIncluded && hasChartOnPage(lane.dashType)
        && UpdateChartIfTrendsAnalysisLane(lane.key, lane.dashType)
        && (!EdelweissAnalytics.openedDetailsGrid[lane.dashType]
            || lane.key === EdelweissAnalytics.LaneKeys.WeedingAnalysis)) {
        componentUpdateFunctions.push(
            async.apply(UpdateLaneCharts, data, lane));
    }

    if (hasDetailsListOnPage(lane.dashType)
        && !EdelweissAnalytics.openedDetailsGrid[lane.dashType]
        && doShowDetailList(lane)) {
        componentUpdateFunctions.push(
            async.apply(UpdateDetailsList, data, lane));
    }

    if (lane.isDetailsGridIncluded && EdelweissAnalytics.isViewingDetailsGrid
        && LaneIsVisible(lane.dashType)
        && UpdateDetailsGridIfTrendsAnalysisLane(lane.key)) {
        componentUpdateFunctions.push(
            async.apply(UpdateDetailsGrid, data, lane));
    }

    async.parallel(componentUpdateFunctions, function (err, result) {
        if (err) {
            return callback(err);
        } else {
            return callback(null);
        }
    });
}

function IsOnEdelweissHome() {
    return $("#dash_200").length > 0;
}
function IsOnAnalyticsHome(dashType) {
    return $("#dash_" + dashType).length > 0;
}
function LaneIsVisible(dashType) {
    if (IsOnEdelweissHome()) {
        return $("#chart_" + dashType + "_0").is(":visible");
    } else if (IsOnAnalyticsHome(dashType)) {
        return $("#dash_" + dashType).is(":visible");
    } else {
        return false;
    }
}
function UpdateDetailsGridIfTrendsAnalysisLane(laneKey) {
    if (laneKey === EdelweissAnalytics.LaneKeys.TrendsAnalysis) {
        return EdelweissAnalytics.isTrendsAnalysisDetailsGridUpdated;
    }
    return true;
}
function UpdateChartIfTrendsAnalysisLane(laneKey, dashType) {
    if (laneKey === EdelweissAnalytics.LaneKeys.TrendsAnalysis) {
        return EdelweissAnalytics.isTrendsAnalysisChartUpdated || EdelweissAnalytics.isFirstTimeLaneLoad[dashType];
    }
    return true;
}

function hasChartOnPage(dashType) {
    return IsOnEdelweissHome() || IsOnAnalyticsHome(dashType);
}

function hasDetailsListOnPage(dashType) {
    return IsOnAnalyticsHome(dashType);
}

function hasFiltersStatusOnPage(laneKey) {
    return $("#filtersStatus_" + laneKey).length > 0;
}

function hasDataDescriptionOnPage(resultType) {
    return $("#dash_ResultHeader_" + resultType).length > 0;
}

function areSearchFiltersSelected(lane) {
    return isCatalogSearchFilterSelected(lane) && isPeerOrgSearchFilterSelected(lane)
        && isPeerBranchSearchFilterSelected(lane);
}
function isCatalogSearchFilterSelected(lane) {
    var isUnselected = lane.isPreferencesDialogBoxIncluded
        && lane.preferencesDialogBox.isCatalogIncluded && lane.search.catalogId <= 0;
    return !isUnselected;
}
function isPeerOrgSearchFilterSelected(lane) {
    var isUnselected = lane.isPreferencesDialogBoxIncluded
        && lane.preferencesDialogBox.isPeerOrgIncluded && lane.search.peerOrgId === "-1";
    return !isUnselected;
}
function isPeerBranchSearchFilterSelected(lane) {
    var isUnselected = lane.isPreferencesDialogBoxIncluded
        && lane.preferencesDialogBox.isPeerBranchIncluded && lane.search.peerBranchId === "-1";
    return !isUnselected;
}

function isCustomCategoryOnlySetWhenAllowed(lane) {
    var isCustomCategorySetWhenNotAllowed = (lane.isSearchIncluded
        && !lane.search.isCustomCategoryAllowed
        && EdelweissAnalytics.isCustomCategoryInAttributeFilters(lane.dashType));
    return !isCustomCategorySetWhenNotAllowed;
}

function shouldSearchProceed(lane) {
    return areSearchFiltersSelected(lane)
        && isCustomCategoryOnlySetWhenAllowed(lane);
}

function isLaneCollapsed(lane, data) {
    return !shouldSearchProceed(lane) || !isProductIdsListValidWhenCollected(lane)
        || noTitleDataReturned(lane, data)
        || notEnoughComparisonData(lane, data);
}

function notEnoughComparisonData(lane, data) {
    if (lane.key === EdelweissAnalytics.LaneKeys.CategoryPerformanceAnalysis
        || lane.key === EdelweissAnalytics.LaneKeys.LocationPerformanceAnalysis) {
        var comparisonData = data.statisticsByComparableType;
        return Object.keys(comparisonData).length === 0;
    } else {
        return false;
    }
}

function notEnoughPOSDataForAllLocations(lane, data) {
    if (lane.key === EdelweissAnalytics.LaneKeys.CategoryPerformanceAnalysis &&
        lane.search.categoryTypeName === getRes('pos_category') &&
        $("#availableBranches").text() === getRes('all_locations')) {
        var comparisonData = data.statisticsByComparableType;
        return Object.keys(comparisonData).length === 0;
    } else {
        return false;
    }
}

function noTitleDataReturned(lane, data) {
    return lane.isSearchIncluded &&
        lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.TitleData
        && parseInt(data.totalTitles) === 0;
}

function doShowDetailList(lane) {
    var noShowDetailList = (lane.key === EdelweissAnalytics.LaneKeys.TrendsAnalysis || lane.key === EdelweissAnalytics.LaneKeys.WeedingAnalysis);
    return !noShowDetailList;
}

EdelweissAnalytics.addSelectedBranchToLocationFilters = function (dashType) {
    var selectedBranch = $("#availableBranches").attr("val");
    if (typeof selectedBranch !== "undefined") {
        EdelweissAnalytics.filterOptions[dashType].addToLocationFilters(selectedBranch);
    }
};

function SetFilterOptionsOnFirstTimeLaneLoad(lane) {
    EdelweissAnalytics.filterOptions[lane.dashType].stockAnalysisClass = lane.search.stockAnalysisClass;
    EdelweissAnalytics.filterOptions[lane.dashType].addToCatalogFilters(lane.search.catalogId);
    EdelweissAnalytics.filterOptions[lane.dashType].catalogName = lane.search.catalogName;
    EdelweissAnalytics.filterOptions[lane.dashType].addToMarketFilters(lane.search.market);
    EdelweissAnalytics.filterOptions[lane.dashType].monthsBack = lane.search.timeFrame;
    EdelweissAnalytics.filterOptions[lane.dashType].numberRequested = lane.search.numberRequested;
    EdelweissAnalytics.filterOptions[lane.dashType].minimumCopiesOnHand = lane.search.minimumCopiesOnHand;
    EdelweissAnalytics.filterOptions[lane.dashType].minimumLastSoldDate = lane.search.defaultMinimumLastSoldDate;
    EdelweissAnalytics.filterOptions[lane.dashType].referenceCode = lane.search.referenceCode;
    EdelweissAnalytics.filterOptions[lane.dashType].peerOrgId = lane.search.peerOrgId;
    EdelweissAnalytics.filterOptions[lane.dashType].peerBranchId = lane.search.peerBranchId;
    EdelweissAnalytics.filterOptions[lane.dashType].wedgeColors = EdelweissAnalytics.stockAnalysisWedgeColorsBySegmentationMode[lane.search.defaultSegmentationMode];
    EdelweissAnalytics.addSelectedBranchToLocationFilters(lane.dashType);
    EdelweissAnalytics.filterOptions[lane.dashType].includeNeverStockedSegment = lane.search.includeNeverStockedSegment;
    EdelweissAnalytics.filterOptions[lane.dashType].searchName = lane.header.title;

    EdelweissAnalytics.filterOptions[lane.dashType].includeTitlesFromAllStockAnalysisClasses = lane.search.includeTitlesFromAllStockAnalysisClasses;

    EdelweissAnalytics.filterOptions[lane.dashType].inventoryIndexRangeLower = lane.search.inventoryIndexRangeLower;
    EdelweissAnalytics.filterOptions[lane.dashType].inventoryIndexRangeUpper = lane.search.inventoryIndexRangeUpper;
    EdelweissAnalytics.filterOptions[lane.dashType].turnRangeLower = lane.search.turnRangeLower;
    EdelweissAnalytics.filterOptions[lane.dashType].turnRangeUpper = lane.search.turnRangeUpper;
    EdelweissAnalytics.filterOptions[lane.dashType].segmentationMode = lane.search.defaultSegmentationMode;

    if (lane.isPreferencesDialogBoxIncluded && lane.preferencesDialogBox.isSegmentationModeIncluded) {
        EdelweissAnalytics.filterOptions[lane.dashType].canSelectFromMultiSegmentationModes = true;
    }

    if (lane.key === EdelweissAnalytics.LaneKeys.CategoryPerformanceAnalysis) {
        EdelweissAnalytics.categoryTypeForComparison = lane.search.categoryType;
    }
    if (lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.TitleData
        || lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.WeedingAnalysisData) {
        EdelweissAnalytics.filterOptions[lane.dashType].excludedProductIdsType =
            EdelweissAnalytics.Exclusions.excludedProductIdsType.sku;
    }

    SetIncludeNotInStockAndNotOnOrder(lane.key, lane.dashType);
    SetDoSolrServerPaging(lane.key, lane.dashType);
    SetRestrictNotInStockAndNotOnOrderByMinimumLastSoldDate(lane.key, lane.dashType);

    if (lane.isPreferencesDialogBoxIncluded && lane.preferencesDialogBox.isPubDateRangeIncluded) {
        if (!_.isEmpty(lane.search.pubDateLowerBoundString) && !_.isEmpty(lane.search.pubDateUpperBoundString)) {
            EdelweissAnalytics.filterOptions[lane.dashType].pubDateFilter = "["
                + lane.search.pubDateLowerBoundString + " TO " + lane.search.pubDateUpperBoundString + "]";
        }
    }

    var selectedFilterId = Number($("#savedFilterName").attr("data-viewid")) || 0;
    var selectedFilterName = selectedFilterId <= 0 ? "" : $("#savedFilterName").html();
    EdelweissAnalytics.filterOptions[lane.dashType].selectedFilterName = selectedFilterName;
}

function SetIncludeNotInStockAndNotOnOrder(laneKey, dashType) {
    if (laneKey === EdelweissAnalytics.LaneKeys.MostPopularTitles
        || laneKey === EdelweissAnalytics.LaneKeys.NotYetReleasedTitles
        || laneKey === EdelweissAnalytics.LaneKeys.CatalogAnalysis
        || laneKey === EdelweissAnalytics.LaneKeys.TitleSetStockAnalysis
        || laneKey === EdelweissAnalytics.LaneKeys.RestockSupport
        || IsRetailStockAnalysisView(laneKey)) {
        EdelweissAnalytics.filterOptions[dashType].includeNotInStockAndNotOnOrder = true;
    }
}

function SetDoSolrServerPaging(laneKey, dashType) {
    var doSolrServerPagingLaneKeys = [
        EdelweissAnalytics.LaneKeys.StockAnalysis,
        EdelweissAnalytics.LaneKeys.PeerBranchStockAnalysis,
        EdelweissAnalytics.LaneKeys.RestockSupport
    ];
    EdelweissAnalytics.filterOptions[dashType].doSolrServerPaging =
        doSolrServerPagingLaneKeys.includes(laneKey);
}

function SetRestrictNotInStockAndNotOnOrderByMinimumLastSoldDate(laneKey, dashType) {
    if (IsRetailStockAnalysisView(laneKey)) {
        EdelweissAnalytics.filterOptions[dashType].restrictNotInStockAndNotOnOrderByMinimumLastSoldDate = true;
    }
}

function IsRetailStockAnalysisView(laneKey) {
    return EdelweissAnalytics.doUseRetailView && laneKey === EdelweissAnalytics.LaneKeys.StockAnalysis;
}

function SetFilterOptionsForEveryLaneLoad(lane) {
    if (!_.isEmpty(lane.search.sortDirection) && !_.isEmpty(lane.search.sortColumn)) {
        EdelweissAnalytics.filterOptions[lane.dashType].sortDirection = lane.search.sortDirection;
        EdelweissAnalytics.filterOptions[lane.dashType].sortColumnName = lane.search.sortColumn;
    } else if (lane.search.titleDefaultSort !== null) {
        var stockAnalysisClass = EdelweissAnalytics.filterOptions[lane.dashType].stockAnalysisClass;
        var stockAnalysisClassString = _.invert(EdelweissAnalytics.StockAnalysisClassUsingActivityIndex)[stockAnalysisClass];
        EdelweissAnalytics.filterOptions[lane.dashType].sortDirection = lane.search.titleDefaultSort.
            sortDirectionByStockAnalysisClass[stockAnalysisClassString];
        EdelweissAnalytics.filterOptions[lane.dashType].sortColumnName = lane.search.titleDefaultSort.columnName;
    }

    switch (EdelweissAnalytics.filterOptions[lane.dashType].segmentationMode) {
        case EdelweissAnalytics.segmentationModes.Turn:
            EdelweissAnalytics.filterOptions[lane.dashType].segmentationRangeLower = EdelweissAnalytics.filterOptions[lane.dashType].turnRangeLower;
            EdelweissAnalytics.filterOptions[lane.dashType].segmentationRangeUpper = EdelweissAnalytics.filterOptions[lane.dashType].turnRangeUpper;
            break;
        case EdelweissAnalytics.segmentationModes.Activity:
            EdelweissAnalytics.filterOptions[lane.dashType].segmentationRangeLower = EdelweissAnalytics.filterOptions[lane.dashType].inventoryIndexRangeLower;
            EdelweissAnalytics.filterOptions[lane.dashType].segmentationRangeUpper = EdelweissAnalytics.filterOptions[lane.dashType].inventoryIndexRangeUpper;
            break;
    }
}

function GetProductIds(lane, callback) {
    if (lane.key === EdelweissAnalytics.LaneKeys.TitleSetStockAnalysis) {
        if (window.rows !== null && window.rows.length > 0) {
            EdelweissAnalytics.filterOptions[lane.dashType].productIds = _.map(window.rows, "item");
        } else if (window.items !== null && window.items.length > 0) {
            EdelweissAnalytics.filterOptions[lane.dashType].productIds = window.items;
        } else {
            EdelweissAnalytics.filterOptions[lane.dashType].productIds = [];
        }
        return callback(null);
    } else if (isProductIdsListCollected(lane)) {
        EdelweissAnalytics.filterOptions[lane.dashType].productIds = [];
        EdelweissAnalytics.filterOptions[lane.dashType].familyKeys = [];
        var family_mode = (EdelweissAnalytics.filterOptions[lane.dashType].referenceCode === EdelweissAnalytics.referenceCodes.FamilyView);
        var productIdEndpoint = lane.search.productIdsSource;
        if (family_mode) {
            productIdEndpoint += "?" + $.param({ returnFamilyKeys: true });
        }
        $.ajax({
            type: "POST",
            url: productIdEndpoint,
            data: EdelweissAnalytics.filterOptions[lane.dashType],
            async: true,
            success: function (returnedIds) {
                if (family_mode) {
                    EdelweissAnalytics.filterOptions[lane.dashType].familyKeys = returnedIds;
                } else {
                    EdelweissAnalytics.filterOptions[lane.dashType].productIds = returnedIds;
                }
                return callback(null);
            },
            error: function () {
                EdelweissAnalytics.filterOptions[lane.dashType].productIds = null;
                EdelweissAnalytics.filterOptions[lane.dashType].familyKeys = null;
                return callback(null);
            },
            datatype: "json"
        });
    } else {
        EdelweissAnalytics.filterOptions[lane.dashType].productIds = [];
        return callback(null);
    }
}

function GetData(lane, callback) {
    if (!isProductIdsListValidWhenCollected(lane)) {
        var nullData = null;
        return callback(null, nullData);
    }

    var analysisApiFilterOptions = EdelweissAnalytics.filterOptions[lane.dashType];

    if (lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.CategoryPerformanceData) {
        if (EdelweissAnalytics.rootCategoryValueForComparison.categoryCode === "") {
            var analysisApiUrl = lane.search.dataSource.apiUrl + lane.key
                + "/" + EdelweissAnalytics.categoryTypeForComparison;
        }
        else {
            var analysisApiUrl = lane.search.dataSource.apiUrl + lane.key
                + "/" + EdelweissAnalytics.categoryTypeForComparison
                + "/" + encodeURIComponent(EdelweissAnalytics.rootCategoryValueForComparison.categoryCode);
        }
    }
    else if (lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.TitleData
        || lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.LocationPerformanceData
        || lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.TrendsAnalysisData) {
        if (lane.key === EdelweissAnalytics.LaneKeys.MostPopularTitles ||
            lane.key === EdelweissAnalytics.LaneKeys.NotYetReleasedTitles) {
            analysisApiFilterOptions = _.cloneDeep(analysisApiFilterOptions);
            analysisApiFilterOptions.attributeFilters = [];
        }
        var analysisApiUrl = lane.search.dataSource.apiUrl + lane.key;
        var isTurnMode = (EdelweissAnalytics.filterOptions[lane.dashType].segmentationMode === EdelweissAnalytics.segmentationModes.Turn);
        var isFamilyView = (EdelweissAnalytics.filterOptions[lane.dashType].referenceCode === EdelweissAnalytics.referenceCodes.FamilyView);
        if (isTurnMode && !isFamilyView) {
            analysisApiUrl = "/api/v1/analysis/stock/turns/" + lane.key;
        }
    } else if (lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.WeedingAnalysisData) {
        var analysisApiUrl = lane.search.dataSource.apiUrl + "/" + EdelweissAnalytics.selectedWeedingType;
    } else {
        return callback(new Error("Invalid data source when trying to retrieve the lane's data."));
    }

    $.ajax({
        type: lane.search.dataSource.apiHttpMethod,
        url: analysisApiUrl,
        data: analysisApiFilterOptions,
        async: true,
        success: function (data) {
            if (LaneIsVisible(lane.dashType)
                && (lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.TitleData
                    || lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.TrendsAnalysisData)) {
                cacheRetrievedAnalyticsData(lane, data);
            } else if (LaneIsVisible(lane.dashType)) {
                if (lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.WeedingAnalysisData) {
                    EdelweissAnalytics.analysisSetSize = data.records.length;
                    EdelweissAnalytics.numberOfSuggestionsByWeedingType = data.countByWeedingType;
                    EdelweissAnalytics.weedingAnalysisRecords = data.records;
                } else if (lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.CategoryPerformanceData) {
                    EdelweissAnalytics.categoryPerformanceComparisonRecords = data.categoryPerformanceComparisonRecords;
                }
            }
            return callback(null, data);
        },
        error: function (err) {
            var message = window.getRes('error_while_loading_analysis');
            displayAnalyticsLaneStatusMessage(message, message, lane);
            EdelweissAnalytics.collapseLane(lane.dashType, lane.laneSizeCssClass);
            return callback(new Error("There was an error loading the lane's data."));
        },
        datatype: "json"
    });
}

// This function is used to store data for title type lanes
function cacheRetrievedAnalyticsData(lane, data) {
    if (!data.hasOwnProperty("marketDataBySku")) {
        data.marketDataBySku = [];
    }
    EdelweissAnalytics.addToMarketDataBySkuCache(lane.dashType, data.marketDataBySku);

    if (!data.hasOwnProperty("skuList") || !Array.isArray(data.skuList)) {
        data.skuList = [];
    }
    var displaySkuList;
    if (data.skuList.length >= EdelweissAnalytics.displayOptions.maxListView) {
        displaySkuList = data.skuList.slice(0, EdelweissAnalytics.displayOptions.maxListView);
    } else {
        displaySkuList = data.skuList;
    }
    EdelweissAnalytics.dashTypeSkuCache[lane.dashType] = displaySkuList;

    EdelweissAnalytics.filterOptions[lane.dashType].analysisCacheKey = data.analysisCacheKey;
    EdelweissAnalytics.filterOptions[lane.dashType].filtersCacheKey = data.filtersCacheKey;
    EdelweissAnalytics.stockAnalysisClasses[lane.dashType] = EdelweissAnalytics.filterOptions[lane.dashType].stockAnalysisClass;
    if (EdelweissAnalytics.isViewingDetailsGrid) {
        //store these individual objects for analysisExport: ExportOrder method in exportTitles_Reports.ascx
        // no dashType in ExportOrder is available so far. I may change the logic to pass the dashType to it in another card
        // so that EdelweissAnalytics.filterOptions[lane.dashType].analysisCacheKey and EdelweissAnalytics.stockAnalysisClasses can be directly used.
        EdelweissAnalytics.analysisCacheKey = data.analysisCacheKey;
        EdelweissAnalytics.stockAnalysisClass = EdelweissAnalytics.filterOptions[lane.dashType].stockAnalysisClass;
        EdelweissAnalytics.filtersCacheKey = data.filtersCacheKey;
    }
    if (lane.key === EdelweissAnalytics.LaneKeys.TrendsAnalysis) {
        if (data.hasOwnProperty("jqueryDataTable") && data.jqueryDataTable.hasOwnProperty("iTotalDisplayRecords")) {
            EdelweissAnalytics.analysisSetSize = data.jqueryDataTable.iTotalDisplayRecords;
        }
    } else {
        EdelweissAnalytics.analysisSetSize = parseInt(data.numSelected.replace(/,/g, ""));
    }
}

function displayAnalyticsLaneStatusMessage(edelweissHomeMessage, analyticsHomeMessage, lane) {
    if (IsOnEdelweissHome()) {
        $("#chartName_" + lane.key + "_0").show();
        if (lane.isChartIncluded && Array.isArray(lane.charts) && lane.charts.length > 0) {
            $("#headerNum" + lane.key + "_0").html(lane.charts[0].header);
        }
        $("#warningMessage_" + lane.key + "_0").show();
        $("#warningMessage_" + lane.key + "_0").html(edelweissHomeMessage);
    } else {
        $("#analyticsLaneStatusMessage_" + lane.key).show();
        $("#analyticsLaneStatusMessage_" + lane.key).text(analyticsHomeMessage);
    }
}

function clearAnalyticsLaneStatusMessage(laneKey) {
    $("#analyticsLaneStatusMessage_" + laneKey).text("");
    $("#analyticsLaneStatusMessage_" + laneKey).hide();
}

function updateStatusMessageAsLaneIsCollapsed(lane, data) {
    if (!areSearchFiltersSelected(lane)) {
        if (!isCatalogSearchFilterSelected(lane)) {
            var searchFilterDisplayName = "catalog or collection";
        } else if (!isPeerOrgSearchFilterSelected(lane)) {
            var searchFilterDisplayName = "peer";
        } else if (!isPeerBranchSearchFilterSelected(lane)) {
            var searchFilterDisplayName = "location";
        } else {
            var searchFilterDisplayName = "search Filter";
        }
        var edelweissHomeMessage = getRes("go_to_this_analysis_x").replace("{searchFilterDisplayName}", searchFilterDisplayName);
        var analyticsHomeMessage = getRes("select_y_to_analyze").replace("{searchFilterDisplayName}", searchFilterDisplayName);
    } else if (!isCustomCategoryOnlySetWhenAllowed(lane)) {
        var edelweissHomeMessage = getRes("custom_categories_not_applicable");
        var analyticsHomeMessage = edelweissHomeMessage;
    } else if (!isProductIdsListValidWhenCollected(lane)
        || noTitleDataReturned(lane, data)) {
        var warningMessage = "";
        if (lane.key === EdelweissAnalytics.LaneKeys.CatalogAnalysis) {
            warningMessage = getRes("catalog_analysis_warning_message");
        } else {
            warningMessage = getRes("no_titles_meet_search_criteria");
        }
        var edelweissHomeMessage = warningMessage;
        var analyticsHomeMessage = warningMessage;
    } else if (notEnoughComparisonData(lane, data)) {
        if (notEnoughPOSDataForAllLocations(lane, data)) {
            var warningMessage = getRes("note_all_location_in_category_comparison");
        } else {
            var warningMessage = getRes("not_enough_data_objects");
        }
        var edelweissHomeMessage = warningMessage;
        var analyticsHomeMessage = warningMessage;
    }
    displayAnalyticsLaneStatusMessage(edelweissHomeMessage, analyticsHomeMessage, lane);
}

EdelweissAnalytics.hideSelectAllTitlesButton = function (laneKey) {
    $(".analyticsLane[data-lanekey=" + laneKey + "] .analytics-showAllTitlesButton").hide();
};
EdelweissAnalytics.showSelectAllTitlesButton = function (laneKey) {
    $(".analyticsLane[data-lanekey=" + laneKey + "] .analytics-showAllTitlesButton").show();
};
EdelweissAnalytics.appendTextForSelectAllTitlesButton = function (laneKey, totalTitles) {
    $(".analyticsLane[data-lanekey=" + laneKey + "] .analytics-showAllTitlesButton").html(getRes("show_all_x").replace("{totaltitles}", totalTitles));
}
EdelweissAnalytics.showAnalyticsAllChartsFooter = function (laneKey) {
    $(".analyticsLane[data-lanekey=" + laneKey + "] .analyticsAllChartsFooter").show();
};
EdelweissAnalytics.hideAnalyticsAllChartsFooter = function (laneKey) {
    $(".analyticsLane[data-lanekey=" + laneKey + "] .analyticsAllChartsFooter").hide();
};

EdelweissAnalytics.getChartSummaryParameters = function (analysisChartSummary, laneKey, monthsBack, selectedFilterName,
    allowTimeFrameChange) {
    var sales = 0.0;
    var avgInventory = 0.0;
    var avgTurn = 0.0;

    if (typeof (analysisChartSummary) !== "undefined") {
        if (EdelweissAnalytics.doUseRetailView) {
            if (typeof (analysisChartSummary.totalRevenues) !== "undefined") {
                sales = analysisChartSummary.totalRevenues;
            }
            if (typeof (analysisChartSummary.actAvgInventoryValue) !== "undefined") {
                avgInventory = analysisChartSummary.actAvgInventoryValue;
            }
        } else {
            if (typeof (analysisChartSummary.totalSales) !== "undefined") {
                sales = analysisChartSummary.totalSales;
            }
            if (typeof (analysisChartSummary.actualAvgInventory) !== "undefined") {
                avgInventory = analysisChartSummary.actualAvgInventory;
            }
        }
        if (typeof (analysisChartSummary.avgTurns) !== "undefined") {
            avgTurn = analysisChartSummary.avgTurns;
        }
    }

    var summaryParameters = {
        laneKey: laneKey,
        sales: sales,
        avgInventory: avgInventory,
        avgTurn: avgTurn,
        timeFrame: monthsBack,
        selectedFilterName: selectedFilterName,
        allowTimeFrameChange: allowTimeFrameChange
    };

    return summaryParameters;
}

function UpdateChartSummary(data, lane, callback) {
    if (isLaneCollapsed(lane, data)) {
        if (IsOnAnalyticsHome(lane.dashType)) {
            $("#chartSummary_" + lane.dashType).empty();
        }
        return callback(null, "No summary shown.");
    }

    if ($("#chartSummary_" + lane.dashType).length > 0) {
        $("#chartSummary_" + lane.dashType).empty();
    }

    var appliedFilterName;
    if ($("#savedFilterName").attr("data-viewid") != 0) {
        appliedFilterName = $("#savedFilterName").html();
    } else {
        appliedFilterName = getRes("no_filter");
    }
    var temporaryCategoryFilterObject = EdelweissAnalytics.getCurrentTemporaryCategoryFilterObject();
    if (!_.isEmpty(temporaryCategoryFilterObject)) {
        var temporaryCategoryFilterName = temporaryCategoryFilterObject.categoryName;
        if ($("#savedFilterName").attr("data-viewid") != 0) {
            appliedFilterName += " " + temporaryCategoryFilterName;
        } else {
            appliedFilterName = temporaryCategoryFilterName;
        }
    }

    var summaryParameters = EdelweissAnalytics.getChartSummaryParameters(data.analysisChartSummary,
        lane.key, EdelweissAnalytics.filterOptions[lane.dashType].monthsBack, appliedFilterName, true);

    $.url = "/GetTreelineControl.aspx?controlName=/uc/analytics/ChartSummary.ascx&" + $.param(summaryParameters);

    $("#chartSummary_" + lane.dashType).load($.url, function (response, status, xhr) {
        if (status === "error") {
            return callback(new Error("Error: " + xhr.status + " " + xhr.statusText));
        } else {
            return callback(null, "Successful chart summary load");
        }
    });

}

function UpdateLaneCharts(data, lane, callback) {
    if (isLaneCollapsed(lane, data)) {
        if (IsOnAnalyticsHome(lane.dashType)) {
            $("#chartName_" + lane.key + "_0").empty();
            $("#headerNum" + lane.key + "_0").empty();
            $("#chartFrame" + lane.key + "_0").empty();
            EdelweissAnalytics.hideSelectAllTitlesButton(lane.key);
            EdelweissAnalytics.hideAnalyticsAllChartsFooter(lane.key);
            EdelweissAnalytics.Exclusions.hideExclusionsMessageContainer(lane.key);
        }
        // else, on edelweiss home, so let warning message remain within the chart
        return callback(null, "Chart not drawn.");
    }
    var updateChartFunctions = [];
    var chartsToUpdate = getChartsToProcess(lane);
    var numberOfCharts = chartsToUpdate.length;

    for (i = 0; i < numberOfCharts; i++) {
        updateChartFunctions.push(
            async.apply(UpdateChart, data, lane, chartsToUpdate[i], i));
    }
    async.parallel(updateChartFunctions, function (err, results) {
        if (err) {
            return callback(err);
        } else {
            return callback(null);
        }
    });
}

function UpdateChart(data, lane, chart, chartIndex, callback) {
    if (lane.key === EdelweissAnalytics.LaneKeys.WeedingAnalysis
        && chart.type === EdelweissAnalytics.chartType.barChart) {
        if (EdelweissAnalytics.selectedWeedingType === EdelweissAnalytics.weedingType.all) {
            EdelweissAnalytics.Weeding.showWeedingBarChart().then(function () {
                return callback(null);
            }).catch(function (error) {
                console.log(error);
                return callback(new Error("Error drawing weeding bar charts"));
            });
            return;
        } else {
            return callback(null);
        }
    }

    var header = chart.header;
    EdelweissAnalytics.showSelectAllTitlesButton(lane.key);
    if (lane.key === EdelweissAnalytics.LaneKeys.TrendsAnalysis) {
        EdelweissAnalytics.showAnalyticsAllChartsFooter(lane.key);
    }

    if (lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.TitleData
        && typeof (data.excludedProductIds) !== "undefined" && data.excludedProductIds !== null) {
        EdelweissAnalytics.Exclusions.updateExclusionsMessage(data.excludedProductIds, lane.key);
    }

    if (lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.TitleData) {
        EdelweissAnalytics.appendTextForSelectAllTitlesButton(lane.key, data.totalTitles);
    }

    var isIsbnView = (EdelweissAnalytics.filterOptions[lane.dashType].referenceCode === EdelweissAnalytics.referenceCodes.IsbnView);

    var primaryMessage = "";
    var secondaryMessage = "";
    if (chart.type === EdelweissAnalytics.chartType.heatMapChart) {
        if (lane.key === EdelweissAnalytics.LaneKeys.CategoryPerformanceAnalysis) {
            primaryMessage = getRes("distribution_of_categories");
        } else if (lane.key === EdelweissAnalytics.LaneKeys.LocationPerformanceAnalysis) {
            primaryMessage = getRes("distribution_of_branches");
        } else {
            primaryMessage = EdelweissAnalytics.doUseRetailView ? "Turns vs Inventory" : "Circulation vs Holdings";
        }
    }

    else if (chart.type === EdelweissAnalytics.chartType.pieChart) {
        primaryMessage = getRes("x_titles_total").replace("{totaltitles}", data.totalTitles);
        secondaryMessage = getRes("x_selected").replace("{numSelected}", data.numSelected);
        var stockAnalysisClass = EdelweissAnalytics.filterOptions[lane.dashType].stockAnalysisClass;

        var segmentationMode = EdelweissAnalytics.filterOptions[lane.dashType].segmentationMode;
        var isSegmentationModeSwitchIncluded = (lane.isPreferencesDialogBoxIncluded && lane.preferencesDialogBox.isSegmentationModeIncluded);

        // TODO: remove the ISBN view check condition after the stock analysis by turn is implemented for the family view
        if (isSegmentationModeSwitchIncluded && isIsbnView) {
            if (segmentationMode === EdelweissAnalytics.segmentationModes.Turn) {
                if (!EdelweissAnalytics.filterOptions[lane.dashType].includeNeverStockedSegment) {
                    primaryMessage = getRes("x_titles_total").replace("{totaltitles}", data.stockedOnlyTitles);
                    EdelweissAnalytics.appendTextForSelectAllTitlesButton(lane.key, data.stockedOnlyTitles);
                }
            }
        }
    }
    else if (chart.type === EdelweissAnalytics.chartType.lineChart) {
        primaryMessage = chart.header;
    }

    EdelweissAnalytics.updateChartHeadersFootersByLaneKey(lane.dashType, lane.resultType, lane.key, chartIndex,
        header, primaryMessage, secondaryMessage);

    if (chart.type === EdelweissAnalytics.chartType.pieChart) {
        drawPieChart(data, lane, chartIndex, callback);
    } else if (chart.type === EdelweissAnalytics.chartType.heatMapChart) {
        var comparisonData = data.statisticsByComparableType;
        EdelweissAnalytics.drawHeatMapChart(lane, chart, chartIndex, comparisonData, callback);
    } else if (chart.type === EdelweissAnalytics.chartType.lineChart) {
        var graphData = EdelweissAnalytics.getTrendsGraphDataFromGraphType(chart.headerId, data);
        if (graphData) {
            if (chart.headerId === EdelweissAnalytics.trendsAnalysisGraphType.shareOfTotalStore) {
                window.EdelweissAnalytics.drawShareOfTotalStoreLineChart(
                    graphData, lane.key, lane.dashType, chartIndex, callback);
            } else {
                EdelweissAnalytics.drawLineChart(graphData, lane.key, lane.dashType, chart, chartIndex, false, callback);
            }
        }
    }
    else {
        return callback(new Error("Invalid parameters caused no chart to be drawn"));
    }
}

EdelweissAnalytics.getTrendsGraphDataFromGraphType = function (graphType, trendsData) {
    var graphData;
    switch (graphType) {
        case EdelweissAnalytics.trendsAnalysisGraphType.circulation:
            graphData = trendsData.unitSalesTrendTable;
            break;
        case EdelweissAnalytics.trendsAnalysisGraphType.inventory:
            graphData = trendsData.inventoryTrendTable;
            break;
        case EdelweissAnalytics.trendsAnalysisGraphType.turn:
            graphData = trendsData.turnsTrendTable;
            break;
        case EdelweissAnalytics.trendsAnalysisGraphType.revenues:
            graphData = trendsData.salesTrendTable;
            break;
        case EdelweissAnalytics.trendsAnalysisGraphType.shareOfTotalStore:
            graphData = trendsData.shareOfTotalStoreTrendTable;
    }
    return graphData;
};

EdelweissAnalytics.drawHeatMapChart = function (lane, chart, chartIndex, data, callback) {
    if (Object.keys(data).length > 0) {
        var cellArr = [];
        var numElementsPerCellArr = [];
        for (var key in data) {
            cellArr.push(key);
            numElementsPerCellArr.push(data[key].length);
        }

        $.url = "/GetTreelineControl.aspx?controlName=/uc/analytics/dashContent/Analytics_HeatMapChart.ascx"
            + "&laneKey=" + lane.key
            + "&cellList=" + cellArr.join(",")
            + "&numElementsPerCellList=" + numElementsPerCellArr.join(",");

        $("#chartFrame" + lane.key + "_" + chartIndex).load($.url, function (response, status, xhr) {
            if (status === "error") {
                return callback(new Error("Error: " + xhr.status + " " + xhr.statusText));
            }
            var resultType = lane.resultType;
            var dashType = lane.dashType;
            var dataSourceKey = lane.search.dataSource.dataSourceKey;
            if (chart.headerId) {
                var viewType = "IndividualView";
            }
            for (var i = 0; i < cellArr.length; i++) {
                var cellNum = cellArr[i];
                var comparableObjectsCount = data[cellNum].length;
                var comparisonData = {};
                comparisonData[cellNum] = data[cellNum];
                var jsonData = EdelweissAnalytics.formPerformanceComparisonScrollJsonData(comparisonData, dataSourceKey);
                (function (cellNum, resultType, dashType, comparableObjectsCount, jsonData, comparisonData, viewType, laneKey, datSourceId) {
                    $("#heatMapChart" + laneKey).find("#cell_" + cellNum).click(function () {
                        window.populatePerformanceComparisonInfoScroll(resultType, dashType, comparableObjectsCount, jsonData,
                            comparisonData, viewType, lane.key, dataSourceKey, cellNum);
                    });
                })(cellNum, resultType, dashType, comparableObjectsCount, jsonData, comparisonData, viewType, lane.key, dataSourceKey);
            }

            return callback(null, "Drawing heat map chart complete");
        });
    }
}
function drawPieChart(data, lane, chartIndex, callback) {
    var jsonData = data.data;
    if (window.drawStandardPie && jsonData.hasOwnProperty("cols")) {
        window.drawStandardPie(lane.dashType, jsonData, EdelweissAnalytics.filterOptions[lane.dashType].wedgeColors,
            "select", function (chart) {

                if (typeof chart.getSelection()[0] !== "undefined") {
                    // On change of the selected pie chart wedge, set the new stock analysis class
                    var newStockAnalysisClass = chart.getSelection()[0].row;
                    EdelweissAnalytics.filterOptions[lane.dashType].stockAnalysisClass = newStockAnalysisClass;
                    EdelweissAnalytics.filterOptions[lane.dashType].includeTitlesFromAllStockAnalysisClasses = false;

                    if (lane.search.isStockAnalysisClassSaved) {
                        EdelweissAnalytics.saveShowAllPreference(lane.key, false, function () {
                            async.series([
                                async.apply(EdelweissAnalytics.saveAnalyticsUserPreference,
                                    lane.search.prefStockAnalysisClassName, newStockAnalysisClass)
                            ], function (err, results) {
                                EdelweissAnalytics.startLaneUpdateProcess(lane.key);
                            });
                        });
                    } else {
                        EdelweissAnalytics.startLaneUpdateProcess(lane.key);
                    }
                }
            }, lane.key, callback, chartIndex);
    }
}

window.EdelweissAnalytics.addTooltipsToShareOfTotalGraph = function (dataTable, trendLineTypes) {
    dataTable.insertColumn(trendLineTypes.sales.tooltipColumnIndex, 'string', 'salesTooltip');
    dataTable.setColumnProperty(trendLineTypes.sales.tooltipColumnIndex, 'role', 'tooltip');
    dataTable.setColumnProperty(trendLineTypes.sales.tooltipColumnIndex, 'html', true);
    dataTable.insertColumn(trendLineTypes.inventory.tooltipColumnIndex, 'string', 'inventoryTooltip');
    dataTable.setColumnProperty(trendLineTypes.inventory.tooltipColumnIndex, 'role', 'tooltip');
    dataTable.setColumnProperty(trendLineTypes.inventory.tooltipColumnIndex, 'html', true);

    var monthColumnIndex = 0;

    var numberOfRows = dataTable.getNumberOfRows();
    for (var i = 0; i < numberOfRows; i++) {
        var monthName = dataTable.getValue(i, monthColumnIndex);
        var thisYear = new Date().getFullYear();
        var selectedMonth = getSelectedMonthFromName(monthName);
        var currentMonth = new Date().getMonth() + 1;
        if (selectedMonth > currentMonth) {
            thisYear--;
        }
        var thisYearSalesValue = dataTable.getValue(i, trendLineTypes.sales.columnIndex);
        var thisYearInventoryValue = dataTable.getValue(i, trendLineTypes.inventory.columnIndex);

        dataTable.setValue(i, trendLineTypes.sales.tooltipColumnIndex,
            createHtmlForTrendTooltip(
                thisYearSalesValue, monthName, thisYear, window.EdelweissAnalytics.valueFormat.percent, false));
        dataTable.setValue(i, trendLineTypes.inventory.tooltipColumnIndex,
            createHtmlForTrendTooltip(
                thisYearInventoryValue, monthName, thisYear, window.EdelweissAnalytics.valueFormat.percent, false));
    }
};

window.EdelweissAnalytics.drawShareOfTotalStoreLineChart = function (
    graphData, laneKey, dashType, chartIndex, callback) {

    var dataTable = new window.google.visualization.DataTable(graphData);

    var trendLineTypes = {
        sales: {
            columnIndex: 1,
            tooltipColumnIndex: 2,
            color: '#529BD4',
            requiredPerformanceActivity: window.getEnumValue("requiredPerformanceActivity", "CIRCULATION")
        },
        inventory: {
            columnIndex: 3,
            tooltipColumnIndex: 4,
            color: '#FC601F',
            requiredPerformanceActivity: window.getEnumValue("requiredPerformanceActivity", "INVENTORY")
        }
    };

    window.EdelweissAnalytics.addTooltipsToShareOfTotalGraph(dataTable, trendLineTypes);

    var options = {
        backgroundColor: '#E9EBEC',
        chartArea: { left: '20%', width: '80%', height: '300px' },
        colors: [trendLineTypes.sales.color, trendLineTypes.inventory.color],
        height: 220,
        width: 480,
        legend: { position: 'top', maxLines: 2 },
        is3D: false,
        fontSize: 10,
        series: {
            0: { lineWidth: 3, pointSize: 0 },
            1: { lineWidth: 3, pointSize: 0 },
        },
        hAxis: {
            title: window.getRes('month'),
            titleTextStyle: { color: 'black', italic: 'false' }
        },
        vAxis: { minValue: 0, format: 'percent' },
        tooltip: { isHtml: true }
    };

    var chartDiv = window.EdelweissAnalytics.getChartDivByLaneKeyAndChartIndex(laneKey, chartIndex);
    var chart = new window.google.visualization.ScatterChart(chartDiv);

    var onPointSelection = function (chart, graphData) {
        window.logPageHit(window.getEnumValue("siteContext", "EDELWEISSANALYTICS"),
            window.EdelweissAnalytics.getSiteAreaFromLaneKey(laneKey), window.EdelweissAnalytics.sessionId);

        var selectedPoint = chart.getSelection()[0];
        if (typeof selectedPoint !== "undefined") {
            var selectedYear = new Date().getFullYear();

            var selectedMonthObject = graphData.rows[selectedPoint.row];
            var selectedMonthName = selectedMonthObject.c[0].v;
            var selectedMonth = getSelectedMonthFromName(selectedMonthName);
            var currentMonth = new Date().getMonth() + 1;
            if (selectedMonth > currentMonth) {
                selectedYear--;
            }

            window.EdelweissAnalytics.filterOptions[dashType].year = selectedYear;
            window.EdelweissAnalytics.filterOptions[dashType].month = selectedMonth;

            var requiredPerformanceActivity;
            if (selectedPoint.column === trendLineTypes.sales.columnIndex) {
                requiredPerformanceActivity = trendLineTypes.sales.requiredPerformanceActivity;
            } else if (selectedPoint.column === trendLineTypes.inventory.columnIndex) {
                requiredPerformanceActivity = trendLineTypes.inventory.requiredPerformanceActivity;
            } else {
                alert(window.getRes("error_unexpected"));
                return callback(null, "Selected column does not exist.");
            }
            window.EdelweissAnalytics.filterOptions[dashType].requiredPerformanceActivity = requiredPerformanceActivity;

            window.EdelweissAnalytics.updateTrendsAnalysisLaneAfterPointSelection(laneKey, dashType);
        }
    };

    window.google.visualization.events.addListener(chart, "select", function () {
        onPointSelection(chart, graphData);
    });

    chart.draw(dataTable, options);

    return callback(null, "Done drawing share of total line chart.");
}

EdelweissAnalytics.drawLineChart = function (jsonData, laneKey, dashType, laneChart, chartIndex, disablePointClickEvent, callback) {
    if (jsonData.hasOwnProperty("cols")) {
        laneKey = typeof laneKey !== "undefined" ? laneKey : null;
        callback = typeof callback !== "undefined" ? callback : null;
        var hasProjectedColumn = false;
        for (var i = 0; i < jsonData.cols.length; i++) {
            if (jsonData.cols[i].label == getRes("projected_month")) {
                hasProjectedColumn = true;
                break;
            }
        }
        var data = new google.visualization.DataTable(jsonData);
        var numberOfRows = data.getNumberOfRows();
        var defaultColors = ['#99CCCC', '#529BD4', '#FC601F'];
        if (hasProjectedColumn) {
            defaultColors.push('#808080');
        }
        var valueFormat = EdelweissAnalytics.valueFormat.number;
        if (laneChart.type === EdelweissAnalytics.chartType.lineChart && (laneChart.headerId === EdelweissAnalytics.trendsAnalysisGraphType.revenues
            || (laneChart.headerId === EdelweissAnalytics.trendsAnalysisGraphType.inventory && EdelweissAnalytics.doUseRetailView))) {
            valueFormat = EdelweissAnalytics.valueFormat.currency;
        }

        if (hasProjectedColumn) {
            data.insertColumn(2, 'string', 'lastYearTooltip');
            data.setColumnProperty(2, 'role', 'tooltip');
            data.setColumnProperty(2, 'html', true);
            data.insertColumn(4, 'string', 'lastYearTooltip');
            data.setColumnProperty(4, 'role', 'tooltip');
            data.setColumnProperty(4, 'html', true);
            data.insertColumn(6, 'string', 'lastYearTooltip');
            data.setColumnProperty(6, 'role', 'tooltip');
            data.setColumnProperty(6, 'html', true);
            data.addColumn({ 'type': 'string', 'role': 'style' });
            data.addColumn({ 'type': 'string', 'role': 'tooltip', 'p': { 'html': true } });
        } else if (numberOfRows > 0) {
            data.insertColumn(2, 'string', 'lastYearTooltip');
            data.setColumnProperty(2, 'role', 'tooltip');
            data.setColumnProperty(2, 'html', true);
            data.insertColumn(4, 'string', 'lastYearTooltip');
            data.setColumnProperty(4, 'role', 'tooltip');
            data.setColumnProperty(4, 'html', true);
            data.addColumn({ 'type': 'string', 'role': 'tooltip', 'p': { 'html': true } });
        }

        if (hasProjectedColumn && numberOfRows > 0) {
            data.setCell(numberOfRows - 1, 8, 'point { size:4; fill-color:#696969 }');
        }
        for (var i = 0; i < numberOfRows; i++) {
            var monthName = data.getValue(i, 0);
            var twoYearsAgo = new Date().getFullYear() - 2;
            var lastYear = new Date().getFullYear() - 1;
            var thisYear = new Date().getFullYear();
            var selectedMonth = getSelectedMonthFromName(monthName);
            var currentMonth = new Date().getMonth() + 1;
            if (selectedMonth > currentMonth) {
                twoYearsAgo--;
                lastYear--;
                thisYear--;
            }
            var twoYearsAgoValue = data.getValue(i, 1);
            var lastYearValue = data.getValue(i, 3);
            var thisYearValue = data.getValue(i, 5);
            if (hasProjectedColumn) {
                if (i === numberOfRows - 2) {
                    data.setValue(numberOfRows - 2, 9, createHtmlForTrendTooltip(thisYearValue, monthName, thisYear, valueFormat, false));
                } else if (i === numberOfRows - 1) {
                    var projectedValue = data.getValue(numberOfRows - 1, 7);
                    data.setValue(numberOfRows - 1, 9, createHtmlForTrendTooltip(projectedValue, monthName, thisYear, valueFormat, true));
                }
            }

            data.setValue(i, 2, createHtmlForTrendTooltip(twoYearsAgoValue, monthName, twoYearsAgo, valueFormat, false));
            data.setValue(i, 4, createHtmlForTrendTooltip(lastYearValue, monthName, lastYear, valueFormat, false));
            data.setValue(i, 6, createHtmlForTrendTooltip(thisYearValue, monthName, thisYear, valueFormat, false));
        }
        var options = {
            backgroundColor: '#E9EBEC',
            chartArea: { left: '20%', width: '80%', height: '300px' },
            colors: defaultColors,
            height: 220,
            width: 480,
            legend: { position: 'top', maxLines: 2 },
            is3D: false,
            fontSize: 10,
            series: {
                0: { lineWidth: 3, pointSize: 0 },
                1: { lineWidth: 3, pointSize: 0 },
                2: { lineWidth: 3, pointSize: 0 },
                3: { lineWidth: 2, lineDashStyle: [10, 2], pointSize: 0.1 }
            },
            hAxis: { title: getRes('month'), titleTextStyle: { color: 'black', italic: 'false' } },
            vAxis: { minValue: 0 }
        };

        if (valueFormat === EdelweissAnalytics.valueFormat.currency) {
            // need double check if all cultures have been considered 
            if (_.includes(EdelweissAnalytics.euroCultureCodes, window.cultureName)) {
                options.vAxis.format = '£###,##0.00';
            } else {
                options.vAxis.format = '$###,##0.00';
            }
        }
        options.tooltip = { isHtml: true };
        var chartDiv = EdelweissAnalytics.getChartDivByLaneKeyAndChartIndex(laneKey, chartIndex);
        var chart = new google.visualization.ScatterChart(chartDiv);

        var onTrendsAnalysisPointSelection = function (chart, jsonData, chartHeaderId) {
            if (IsOnEdelweissHome()) {
                goToAnalyticsHome(200, 222);
                return;
            }

            logPageHit(getEnumValue("siteContext", "EDELWEISSANALYTICS"),
                EdelweissAnalytics.getSiteAreaFromLaneKey(laneKey), EdelweissAnalytics.sessionId);

            var selectedPoint = chart.getSelection()[0];
            if (typeof selectedPoint !== "undefined") {
                var selectedYear;
                if (selectedPoint.column === 5 || selectedPoint.column === 7) {
                    selectedYear = new Date().getFullYear();
                } else if (selectedPoint.column == 3) {
                    selectedYear = new Date().getFullYear() - 1;
                } else if (selectedPoint.column === 1) {
                    selectedYear = new Date().getFullYear() - 2;
                } else {
                    alert(getRes("error_unexpected"));
                    return;
                }

                var selectedMonthObject = jsonData.rows[selectedPoint.row];
                var selectedMonthName = selectedMonthObject.c[0].v;
                var selectedMonth = getSelectedMonthFromName(selectedMonthName);
                var currentMonth = new Date().getMonth() + 1;
                if (selectedMonth > currentMonth) {
                    selectedYear--;
                }

                EdelweissAnalytics.filterOptions[dashType].month = selectedMonth;
                EdelweissAnalytics.filterOptions[dashType].year = selectedYear;

                var performanceActivity;
                if (chartHeaderId === EdelweissAnalytics.trendsAnalysisGraphType.circulation || chartHeaderId === EdelweissAnalytics.trendsAnalysisGraphType.revenues) {
                    performanceActivity = getEnumValue("requiredPerformanceActivity", "CIRCULATION");
                } else if (chartHeaderId === EdelweissAnalytics.trendsAnalysisGraphType.inventory) {
                    performanceActivity = getEnumValue("requiredPerformanceActivity", "INVENTORY");
                } else if (chartHeaderId === EdelweissAnalytics.trendsAnalysisGraphType.turn) {
                    performanceActivity = getEnumValue("requiredPerformanceActivity", "TURN");
                }
                EdelweissAnalytics.filterOptions[dashType].requiredPerformanceActivity = performanceActivity;

                window.EdelweissAnalytics.updateTrendsAnalysisLaneAfterPointSelection(laneKey, dashType);
            }
        };

        google.visualization.events.addListener(chart, "select", function () {
            if (disablePointClickEvent) {
                chart.setSelection([]);
            } else {
                if (laneKey === EdelweissAnalytics.LaneKeys.TrendsAnalysis) {
                    onTrendsAnalysisPointSelection(chart, jsonData, laneChart.headerId);
                }
            }
        });

        chart.draw(data, options);
        if (_.isFunction(callback)) {
            return callback(null, "Drawing line chart complete");
        }
    }
}

window.EdelweissAnalytics.updateTrendsAnalysisLaneAfterPointSelection = function (laneKey, dashType) {
    window.EdelweissAnalytics.isTrendsAnalysisDetailsGridUpdated = true;
    if (!$("#analytics_content").is(":visible") || $("#analytics_content").length === 0) {
        window.EdelweissAnalytics.reLoadAnalyticsDetail[dashType] = true;
    }
    if (window.EdelweissAnalytics.isViewingDetailsGrid) {
        window.EdelweissAnalytics.startLaneUpdateProcess(laneKey);
    } else {
        window.toggleAnalyticsDetailByLaneKey(laneKey, dashType);
    }
};

function createHtmlForTrendTooltip(value, monthName, year, valueFormat, isProjectedDatapoint) {
    var formattedValue;
    switch (valueFormat) {
        case window.EdelweissAnalytics.valueFormat.currency:
            formattedValue = getCurrencyFormattedNumbersUsingLocale(value);
            break;
        case window.EdelweissAnalytics.valueFormat.percent:
            formattedValue = window.EdelweissAnalytics.getPercentFormattedNumbersUsingLocale(value);
            break;
        case window.EdelweissAnalytics.valueFormat.number:
        default:
            formattedValue = getFormattedNumbersUsingLocale(value);
            break;
    }

    var dHtml = '<div style="padding: 5px;"><span style="font-weight: bold;">';
    if (isProjectedDatapoint) {
        dHtml += getRes("projected_value") + ": ";
    }
    dHtml += formattedValue + ' (' + monthName + ' ' + year + ') </span>';
    dHtml += '<br>';
    dHtml += '<span> ' + getRes('click_to_see_titles') + ' </span></div>';

    return dHtml;
}

function getSelectedMonthFromName(monthName) {
    if (monthName === getRes('jan')) {
        return 1;
    }

    if (monthName === getRes('feb')) {
        return 2;
    }

    if (monthName === getRes('mar')) {
        return 3;
    }

    if (monthName === getRes('apr')) {
        return 4;
    }

    if (monthName === getRes('may')) {
        return 5;
    }

    if (monthName === getRes('jun')) {
        return 6;
    }

    if (monthName === getRes('jul')) {
        return 7;
    }

    if (monthName === getRes('aug')) {
        return 8;
    }

    if (monthName === getRes('sep')) {
        return 9;
    }

    if (monthName === getRes('oct')) {
        return 10;
    }

    if (monthName === getRes('nov')) {
        return 11;
    }

    if (monthName === getRes('dec')) {
        return 12;
    }
};

function UpdateDetailsList(data, lane, callback) {
    if (isLaneCollapsed(lane, data)) {
        $("#detail_" + lane.key).empty();
        return callback(null, "Details list not drawn.");
    }

    if (lane.isSearchIncluded) {
        if (lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.CategoryPerformanceData
            || lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.LocationPerformanceData) {
            UpdatePerformanceInfoList(data, lane, callback);
        } else if (lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.TitleData) {
            UpdateTitlesList(data, lane, callback);
        } else {
            return callback(new Error("Invalid data source when trying to update the details list"));
        }
    } else {
        return callback(new Error("Invalid parameters caused the details list to not be updated"));
    }
}

function UpdatePerformanceInfoList(data, lane, callback) {
    var comparisonData = data.statisticsByComparableType;
    var comparableObjectsCount = 0;
    var jsonData;
    var dataSourceKey = lane.search.dataSource.dataSourceKey;
    if (Object.keys(comparisonData).length > 0) {
        jsonData = EdelweissAnalytics.formPerformanceComparisonScrollJsonData(comparisonData, dataSourceKey);
        for (var key in comparisonData) {
            if (comparisonData[key] instanceof Array) {
                comparableObjectsCount += comparisonData[key].length;
            }
        }
        window.populatePerformanceComparisonInfoScroll(lane.resultType, lane.dashType, comparableObjectsCount, jsonData,
            comparisonData, viewType, lane.key, dataSourceKey, undefined, callback);
    }
    else {
        $("#dashboardFrame" + lane.dashType).remove();
        return callback(null, "No enough data for a useful comparison!");
    }
}

EdelweissAnalytics.formPerformanceComparisonScrollJsonData = function (comparisonData, dataSourceKey) {
    var performanceInfoObj = {};
    for (var key in comparisonData) {
        if (comparisonData[key] instanceof Array) {
            performanceInfoObj[key] = {};
            if (dataSourceKey === EdelweissAnalytics.DataSourceKeys.CategoryPerformanceData) {
                performanceInfoObj[key].subCategoryNames = _.map(comparisonData[key], 'categoryName');
                performanceInfoObj[key].subCategoryCodes = _.map(comparisonData[key], 'categoryCode');
            } else if (dataSourceKey === EdelweissAnalytics.DataSourceKeys.LocationPerformanceData) {
                performanceInfoObj[key].locationIds = _.map(comparisonData[key], 'comparableTypeKey');
            }
        }
    }
    return JSON.stringify(performanceInfoObj);
}

EdelweissAnalytics.drillDownCategory = function (categoryCode, categoryName, isLeafCategory) {

    var previousTemporaryCategoryName = "";
    currentTemporaryCategoryFilterObj = EdelweissAnalytics.getCurrentTemporaryCategoryFilterObject();
    if (!_.isEmpty(currentTemporaryCategoryFilterObj)) {
        previousTemporaryCategoryName = currentTemporaryCategoryFilterObj.categoryName;
    }

    $("#ea_temporaryCategory").show();
    AddRemoveTemporaryCategoryFilterEvent(categoryName);

    var categoryObj = {
        categoryCode: categoryCode,
        categoryName: categoryName,
        isLeafCategory: isLeafCategory
    }
    EdelweissAnalytics.temporaryCategoryFilterPath.push(categoryObj);
    var temporaryFilterPathLength = EdelweissAnalytics.temporaryCategoryFilterPath.length;
    if (temporaryFilterPathLength > 1 && EdelweissAnalytics.temporaryCategoryFilterPath[temporaryFilterPathLength - 2].isLeafCategory) {
        EdelweissAnalytics.temporaryCategoryFilterPath.splice(temporaryFilterPathLength - 2, 1);
    }
    updateTemporaryCategoryFilterUI();
    removePreviousTemporaryCategoryFilter(previousTemporaryCategoryName);
    updateAnalyticsLanesWithTemporaryCategoryFilter();

    EdelweissAnalytics.toggleSelectionOfCategoryInLeftNav(EdelweissAnalytics.categoryTypeForComparison, previousTemporaryCategoryName, false);
    EdelweissAnalytics.toggleSelectionOfCategoryInLeftNav(EdelweissAnalytics.categoryTypeForComparison, categoryName, true);
}

EdelweissAnalytics.isLeftNavCategory = function (categoryType) {
    var categoryTypes = [getEnumValue("categoryType", "BISAC"), getEnumValue("categoryType", "STOREPOS")];
    return _.includes(categoryTypes, categoryType);
};

EdelweissAnalytics.isLeftNavCategoryFilterType = function (filterType) {
    var categoryFilterTypes = [getEnumValue("filterType", "CATEGORIES"), getEnumValue("filterType", "POSCATEGORY")];
    return _.includes(categoryFilterTypes, filterType);
};

EdelweissAnalytics.getFilterTypeFromCategoryType = function (categoryType) {
    switch (categoryType) {
        case getEnumValue("categoryType", "BISAC"):
            return getEnumValue("filterType", "CATEGORIES");
        case getEnumValue("categoryType", "STOREPOS"):
            return getEnumValue("filterType", "POSCATEGORY");
        default:
            return null;
    }
};

EdelweissAnalytics.toggleSelectionOfCategoryInLeftNav = function (categoryType, categoryName, doSelect) {
    var $checkboxes = null;
    var categoryTypeInt = parseInt(categoryType);
    if (EdelweissAnalytics.isLeftNavCategory(categoryTypeInt)) {
        var filterType = EdelweissAnalytics.getFilterTypeFromCategoryType(categoryTypeInt);
        if (filterType !== null) {
            $checkboxes = $("#refineFilter" + filterType
                + " .filterElement[data-key='" + CSS.escape(categoryName) + "']").siblings(".filter");
        }
    }
    if ($checkboxes !== null && $checkboxes.length > 0) {
        if (doSelect) {
            $checkboxes.removeClass("box_unchecked").addClass("box_checked");
        } else {
            $checkboxes.removeClass("box_checked").addClass("box_unchecked");
        }
    }
};

function updateAnalyticsLanesWithTemporaryCategoryFilter() {
    var temporaryCategoryFilterObject = EdelweissAnalytics.getCurrentTemporaryCategoryFilterObject();
    var categoryCode = "";
    var categoryName = "";
    var isLeafCategory = false;
    if (!_.isEmpty(temporaryCategoryFilterObject)) {
        categoryCode = temporaryCategoryFilterObject.categoryCode;
        categoryName = temporaryCategoryFilterObject.categoryName;
        isLeafCategory = temporaryCategoryFilterObject.isLeafCategory;
    }

    $.get("/api/v2/analytics/lanes", function (lanes) {
        for (var laneKey in lanes) {
            var lane = lanes[laneKey];
            if (EdelweissAnalytics.isVisibleLane[lane.dashType]) {
                if (lane.key === EdelweissAnalytics.LaneKeys.CategoryPerformanceAnalysis) {
                    if (!isLeafCategory) {
                        EdelweissAnalytics.rootCategoryValueForComparison.categoryCode = categoryCode;
                        EdelweissAnalytics.rootCategoryValueForComparison.categoryName = categoryName;
                        EdelweissAnalytics.startLaneUpdateProcess(laneKey);
                    }
                } else {
                    EdelweissAnalytics.addToAttributeFilters(lane.dashType, categoryName, categoryCode, EdelweissAnalytics.categoryTypeForComparison);
                    EdelweissAnalytics.isTrendsAnalysisChartUpdated = true;
                    EdelweissAnalytics.startLaneUpdateProcess(laneKey);
                }
            }
        }
    });
}

function removePreviousTemporaryCategoryFilter(previousTemporaryCategoryName) {
    dashTypesOfAnalyticsLanes.forEach(function (dashType) {
        EdelweissAnalytics.removeAttributeFilterFromFilterOptions(dashType, previousTemporaryCategoryName);
    });
}

EdelweissAnalytics.getCurrentTemporaryCategoryFilterObject = function () {
    var temporaryFilterPathLength = EdelweissAnalytics.temporaryCategoryFilterPath.length;
    var temporaryCategoryObject = {};
    if (temporaryFilterPathLength > 0) {
        temporaryCategoryObject = EdelweissAnalytics.temporaryCategoryFilterPath[temporaryFilterPathLength - 1];
    }
    return temporaryCategoryObject;
}

function AddRemoveTemporaryCategoryFilterEvent(categoryName) {
    $("#removeTemporaryCategoryFilter").off().on("click", function (event) {
        EdelweissAnalytics.removeTempCategoryFilterAndUpdateAllLanes(categoryName);
        event.stopPropagation();
    });
}

EdelweissAnalytics.removeTemporaryCategoryFilter = function () {
    $("#ea_temporaryCategory").hide();
    EdelweissAnalytics.rootCategoryValueForComparison.categoryCode = "";
    EdelweissAnalytics.rootCategoryValueForComparison.categoryName = "";
    EdelweissAnalytics.temporaryCategoryFilterPath = [];
};

// reset attributefilters and rootcategoryValueForComparison, then update the lane contents
EdelweissAnalytics.removeTempCategoryFilterAndUpdateAllLanes = function (categoryName) {
    EdelweissAnalytics.removeTemporaryCategoryFilter();
    $.get("/api/v2/analytics/lanes", function (lanes) {
        for (var laneKey in lanes) {
            var lane = lanes[laneKey];
            if (EdelweissAnalytics.isVisibleLane[lane.dashType]) {
                if (lane.key !== EdelweissAnalytics.LaneKeys.CategoryPerformanceAnalysis) {
                    EdelweissAnalytics.removeAttributeFilterFromFilterOptions(lane.dashType, categoryName);
                }
                EdelweissAnalytics.isTrendsAnalysisChartUpdated = true;
                EdelweissAnalytics.startLaneUpdateProcess(laneKey);
            }
        }
    });
    EdelweissAnalytics.toggleSelectionOfCategoryInLeftNav(EdelweissAnalytics.categoryTypeForComparison, categoryName, false);
}

function updateTemporaryCategoryFilterUI() {
    $("#temporaryCategoryFilterNamesContainer").empty();

    var temporaryFilterPathLength = EdelweissAnalytics.temporaryCategoryFilterPath.length;
    var dHtml = "";
    var rootCategoryHtml = "<div id ='rootCategoryName' class='temporaryCategoryFilterName navigableTemporaryCategoryFilter'>" + EdelweissAnalytics.categoryNameForComparison + "</div>";
    var currentCatDHtml = "<div id ='temporaryCategoryFilterName' class='temporaryCategoryFilterName'>" + EdelweissAnalytics.temporaryCategoryFilterPath[temporaryFilterPathLength - 1].categoryName + "</div>";
    dHtml += rootCategoryHtml;
    for (var i = 0; i < temporaryFilterPathLength - 1; i++) {
        var categoryCode = EdelweissAnalytics.temporaryCategoryFilterPath[i].categoryCode;
        var categoryName = EdelweissAnalytics.temporaryCategoryFilterPath[i].categoryName;
        dHtml += "<div class='temporaryCategoryFilterName navigableTemporaryCategoryFilter' data-categoryCode ='" + categoryCode + "'>" + categoryName + "</div>";
    }
    dHtml += currentCatDHtml;

    $("#temporaryCategoryFilterNamesContainer").append(dHtml);
    addNavigateUpEventForTemporaryCategoryFilterPath();
}

function addNavigateUpEventForTemporaryCategoryFilterPath() {
    var temporaryFilterPathLength = EdelweissAnalytics.temporaryCategoryFilterPath.length;
    var currentTemporaryCategoryFilterObj = EdelweissAnalytics.getCurrentTemporaryCategoryFilterObject();
    var currentCategoryName = "";
    if (!_.isEmpty(currentTemporaryCategoryFilterObj)) {
        currentCategoryName = currentTemporaryCategoryFilterObj.categoryName;
    }

    for (var i = 0; i < temporaryFilterPathLength - 1; i++) {
        var categoryCode = EdelweissAnalytics.temporaryCategoryFilterPath[i].categoryCode;
        var categoryName = EdelweissAnalytics.temporaryCategoryFilterPath[i].categoryName;
        (function (categoryCode, categoryName, currentCategoryName) {
            var categoryFilterId = "navigableTemporaryCategoryFilter[data-categoryCode='" + categoryCode + "']";
            $(document).off("click", "." + categoryFilterId)
                .on("click", "." + categoryFilterId, function (event) {
                    navigateUpCategory(categoryCode, categoryName, currentCategoryName);
                    event.stopPropagation();
                })
        })(categoryCode, categoryName, currentCategoryName);
    }

    $("#rootCategoryName").off().on("click", function (event) {
        EdelweissAnalytics.removeTempCategoryFilterAndUpdateAllLanes(currentCategoryName);
        event.stopPropagation();
    });

}

function navigateUpCategory(categoryCode, categoryName, previousTemporaryCategoryName) {
    // update the temporaryCategoryFilterPath object
    var categoryIndex = _.findIndex(EdelweissAnalytics.temporaryCategoryFilterPath, function (cat) { return cat.categoryCode == categoryCode; });
    var numberOfCategoriesDeleted = EdelweissAnalytics.temporaryCategoryFilterPath.length - 1 - categoryIndex;
    EdelweissAnalytics.temporaryCategoryFilterPath.splice(categoryIndex + 1, numberOfCategoriesDeleted);


    updateTemporaryCategoryFilterUI();
    AddRemoveTemporaryCategoryFilterEvent(categoryName);
    removePreviousTemporaryCategoryFilter(previousTemporaryCategoryName);
    updateAnalyticsLanesWithTemporaryCategoryFilter();

    EdelweissAnalytics.toggleSelectionOfCategoryInLeftNav(EdelweissAnalytics.categoryTypeForComparison, previousTemporaryCategoryName, false);
    EdelweissAnalytics.toggleSelectionOfCategoryInLeftNav(EdelweissAnalytics.categoryTypeForComparison, categoryName, true);
}

function UpdateTitlesList(data, lane, callback) {
    EdelweissAnalytics.filterOptions[lane.dashType].doNotUseCachedInStockAnalysis = false;
    if (parseInt(data.numSelected.replace(/,/g, "")) > 0) {
        $("#detail_" + lane.key).show();
        var slider = new InfiniteSlider({
            containerId: "detail_" + lane.key,
            initialLoadingText: EdelweissAnalytics.displayOptions.loadingText,
            apiUrl: "api/v2/analysis/titleElements",
            apiUrlParams: {
                laneKey: lane.key
            },
            apiMethod: "POST",
            apiData: EdelweissAnalytics.filterOptions[lane.dashType]
        });
    } else {
        $("#detail_" + lane.key).hide();
    }
    callback(null);
}

EdelweissAnalytics.savePeerBranchAndUpdateLane = function (locationId) {
    $.get("/api/v2/analytics/lanes", function (lanes) {
        for (var laneKey in lanes) {
            var lane = lanes[laneKey];
            if (lane.key === EdelweissAnalytics.LaneKeys.PeerBranchStockAnalysis) {
                (function (lane) {
                    async.series([
                        async.apply(EdelweissAnalytics.saveAnalyticsUserPreference, lane.search.prefPeerBranchId, locationId)
                    ], function (err, results) {
                        if (err) {
                            console.warn(err.message);
                        } else {
                            EdelweissAnalytics.startLaneUpdateProcess(lane.key);
                        }
                    });
                })(lane);
            }
        }
    });
};

function applyInitialDataTableSettings(grid, data) {
    $.fn.dataTableExt.oStdClasses.sStripeEven = "wFil tlList even altRow";
    $.fn.dataTableExt.oStdClasses.sStripeOdd = "wFil tlList odd stdRow";

    emptyAndDestroyAllGrids();

    if (data.hasOwnProperty("jqueryDataTable") && data.jqueryDataTable.hasOwnProperty("headerGrouping")) {
        grid.on("init.dt", function () {
            $("#headerGrouping").remove();
            var headerGrouping = data.jqueryDataTable.headerGrouping;
            var headerGroupingHtml = ConstructHeaderGroupingHtml(headerGrouping);
            if (headerGroupingHtml != null) {
                grid.children("thead").prepend(headerGroupingHtml);
            }
        });
    }
}

function emptyAndDestroyAllGrids() {
    if (oTable !== null) {
        oTable.fnDestroy();
        $("#performanceGrid").empty();
        $("#titleGrid").empty();
    }
}

function UpdateDetailsGrid(data, lane, callback) {
    if (isLaneCollapsed(lane, data)) {
        emptyAndDestroyAllGrids();
        return callback(null, "Details grid not drawn.");
    }

    if (lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.CategoryPerformanceData
        || lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.LocationPerformanceData) {
        var grid = $("#performanceGrid");
        applyInitialDataTableSettings(grid, data);
        updatePerformanceGrid(grid, data, lane, callback);
    } else if (lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.TitleData) {
        var grid = $("#titleGrid");
        applyInitialDataTableSettings(grid, data);
        if (EdelweissAnalytics.filterOptions[lane.dashType].referenceCode === EdelweissAnalytics.referenceCodes.FamilyView) {
            updateTitleGridInFamilyView(grid, data, lane, callback);
        }
        else {
            updateTitleGridInISBNView(grid, data, lane, callback);
        }
    } else if (lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.TrendsAnalysisData) {
        var grid = $("#titleGrid");
        applyInitialDataTableSettings(grid, data);
        updateTitleGridInISBNView(grid, data, lane, callback);
    } else if (lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.WeedingAnalysisData) {
        var grid = $("#weedingGrid");
        applyInitialDataTableSettings(grid, data);
        EdelweissAnalytics.Weeding.updateWeedingGrid(grid, data, lane, callback);
    } else {
        return callback(new Error("Invalid data source when trying to update the details grid"));
    }
}

function updatePerformanceGrid(grid, data, lane, callback) {
    oTable = grid.dataTable({
        language: getDataTableLocalization(),
        "aoColumns": data.jqueryDataTable.aoColumns,
        "aaData": data.jqueryDataTable.aaData,
        "sDom": data.jqueryDataTable.sDom,
        "bAutoWidth": false,
        "fnDrawCallback": function (oSettings) {
            InitializePerformanceGrid(oSettings, grid);
        },
        "columnDefs": [
            EdelweissAnalytics.formatNumbers(data.jqueryDataTable.aoColumns)
        ]
    });
    if (lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.CategoryPerformanceData) {
        addExcelExportButtonToPerformanceGrid();
    }
    return callback(null, "Performance Grid Initialization Complete");
}

function addExcelExportButtonToPerformanceGrid() {
    var csvExportButtonHtml = '<div id="categoryPerformanceCsvExportContainer">'
        + '<span class="categoryPerformanceCsvExportLabel">' + getRes("export_to_file") + '</span>'
        + '<span class="icon-cloud-download iconSVG categoryPerformanceCsvExportIcon" ></span>'
        + '</div>';
    $("#performanceGrid_wrapper .list_header").html(csvExportButtonHtml);
    var userType = EdelweissAnalytics.doUseRetailView ? EdelweissAnalytics.Export.userType.Retailer
        : EdelweissAnalytics.Export.userType.Librarian;
    $("#categoryPerformanceCsvExportContainer").off().on("click", function () {
        EdelweissAnalytics.Export.doJqueryDataTableCsvExport(
            EdelweissAnalytics.Export.exportType.CategoryPerformanceComparison, userType)
    });
}

function getDataTableLocalization() {
    return {
        search: getRes('search') + ':',
        processing: '<i>' + getRes('loading') + '...</i>',
        emptyTable: getRes('datatable_empty_table'),
        info: getRes('datatable_info'),
        infoEmpty: getRes('datatable_info_empty'),
        infoFiltered: getRes('datatable_info_filtered'),
        lengthMenu: getRes('datatable_length_menu'),
        paginate: {
            next: getRes('next'),
            previous: getRes('previous'),
            first: getRes('first'),
            last: getRes('last')
        }
    };
}

function updateTitleGridInISBNView(grid, data, lane, callback) {
    EdelweissAnalytics.rows = [];
    EdelweissAnalytics.selected = 0;

    var options = {
        "dom": '<"list_header">fl<rt>ip',
        "destroy": true,
        "bProcessing": true,
        "language": getDataTableLocalization(),
        "bServerSide": true,
        "bAutoWidth": false,
        "columns": data.jqueryDataTable.aoColumns,
        "data": data.jqueryDataTable.aaData,
        "rowCallback": function (row, data) {
            var index = arrayObjectIndexOf(EdelweissAnalytics.rows, data[15], "item");
            if (index !== -1 && EdelweissAnalytics.rows[index].selected === 1) {
                $(".itemCheck", row).addClass("checkmark_checked");
            }
        },
        "deferLoading": data.jqueryDataTable.iTotalDisplayRecords,
        "fnDrawCallback": function (oSettings) {
            InitializeTitleGridInISBNView(oSettings, grid);
            titleGridPageLengthChange("#titleGrid");
        },
        "paginationType": data.jqueryDataTable.sPaginationType,
        "iDisplayLength": data.jqueryDataTable.iDisplayLength,
        "sServerMethod": "POST",
        "columnDefs": [
            formatDates(data.jqueryDataTable.aoColumns),
            createCompositeColumn(data.jqueryDataTable.aoColumns,
                'col-lastSold', 'col-lastRecd', ['col-lastSold--lastRecd']),
            createCompositeColumn(data.jqueryDataTable.aoColumns,
                'col-isbn13', 'col-isbn10', ['col-isbn'], ['rowSku']),
            createCompositeColumn(data.jqueryDataTable.aoColumns,
                'col-oo', 'col-ooGapFromAve', ['col-oo--ooGapFromAve']),
            createCompositeColumn(data.jqueryDataTable.aoColumns,
                'col-pubDate', 'col-price', ['col-pubDate--price']),
            createCompositeColumn(data.jqueryDataTable.aoColumns,
                'col-title', 'col-author', ['col-title--author'],
                ['clickableTitle', 'titleFlex_Name', 'accFont']),
            createCompositeColumn(data.jqueryDataTable.aoColumns,
                'col-percentThatSold', 'col-totalUnitsSold', ['col-sold']),
            createTripledValuedColumn(data.jqueryDataTable.aoColumns,
                'col-percentThatOrdered', 'col-totalUnitsOrdered', 'col-averageUnitsOrdered', ['col-ordered']),
            createCompositeColumn(data.jqueryDataTable.aoColumns,
                'col-percentThatOwn', 'col-totalUnitsOwned', ['col-owned']),
            createCompositeColumn(data.jqueryDataTable.aoColumns,
                'col-imprint', 'col-format', ['col-imprint--format']),
            createCompositeColumn(data.jqueryDataTable.aoColumns, 'col-circulationIndex',
                'col-turn', ['col-activity--turn']),
            createCompositeColumn(data.jqueryDataTable.aoColumns, 'col-posCategory', '', ['col-posCategory'])
        ]
    };

    if (lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.TitleData) {
        var stockRecordsApiUrl = "/api/v2/analysis/stock/records/"
            + EdelweissAnalytics.filterOptions[lane.dashType].stockAnalysisClass;
        var titleDataOptions = {
            "sAjaxSource": stockRecordsApiUrl,
            "fnServerParams": function (aoData) {
                aoData.push({
                    name: "filtersCacheKey",
                    value: data.filtersCacheKey
                });
                aoData.push({
                    name: "analysisCacheKey",
                    value: data.analysisCacheKey
                });
            }
        };
        _.merge(options, titleDataOptions);
    } else if (lane.search.dataSource.dataSourceKey === EdelweissAnalytics.DataSourceKeys.TrendsAnalysisData) {
        var trendsAnalysisTableApiUrl = "api/v1/analysis/trend/table";
        var trendsDataOptions = {
            "sAjaxSource": trendsAnalysisTableApiUrl,
            "fnServerParams": function (aoData) {
                aoData.push({
                    name: "filtersCacheKey",
                    value: data.filtersCacheKey
                });
            }
        };
        _.merge(options, trendsDataOptions);
    }

    oTable = grid.dataTable(options);

    new $.fn.dataTable.FixedHeader(oTable);

    EdelweissAnalytics.addGridColumnTooltips(grid);

    $("#titleGrid_wrapper .list_header")
        .load("/GetTreelineControl.aspx?controlName=/uc/listviews/menus/ListView_TopMenu.ascx&ResultType=" + lane.resultType
            + "&doHideSortMenuOption=true");

    // Since the wedge was changed, update the parameters used by the sort dialog box with the sort that was used
    var sortDir = getSortDirFromString(data.sortDirection);
    var sortItem = getSortItem(data.sortColumnName, lane.dashType);
    setSortDir(sortDir);
    EdelweissAnalytics.setSortOrd(sortItem);

    EdelweissAnalytics.addHeaderSortClickListener();
    EdelweissAnalytics.setHeaderSortIcon(sortItem, sortDir);

    callback(null, "Title Grid Initialization Complete");
}

function updateTitleGridInFamilyView(grid, data, lane, callback) {
    EdelweissAnalytics.rows = [];
    EdelweissAnalytics.selected = 0;
    var stockRecordsApiUrl = "/api/v2/analysis/stock/records/"
        + EdelweissAnalytics.filterOptions[lane.dashType].stockAnalysisClass;

    oTable = grid.dataTable({
        "dom": '<"list_header">fl<rt>ip',
        "destroy": true,
        "bProcessing": true,
        "language": getDataTableLocalization(),
        "bServerSide": true,
        "bAutoWidth": false,
        "sAjaxSource": stockRecordsApiUrl,
        "fnServerParams": function (aoData) {
            aoData.push({
                name: "filtersCacheKey",
                value: data.filtersCacheKey
            });
            aoData.push({
                name: "analysisCacheKey",
                value: data.analysisCacheKey
            });
        },
        "columns": data.jqueryDataTable.aoColumns,
        "data": data.jqueryDataTable.aaData,
        "rowCallback": function (row, data) {
            var index = arrayObjectIndexOf(EdelweissAnalytics.rows, data[8], "item");
            if (index !== -1 && EdelweissAnalytics.rows[index].selected === 1) {
                $(".itemCheck", row).addClass("checkmark_checked");
            }
        },
        "fnDrawCallback": function (oSettings) {
            InitializeTitleGridInFamilyView(oSettings, grid, lane.dashType);
            titleGridPageLengthChange("#titleGrid");
        },
        "deferLoading": data.jqueryDataTable.iTotalDisplayRecords,
        "paginationType": data.jqueryDataTable.sPaginationType,
        "iDisplayLength": data.jqueryDataTable.iDisplayLength,
        "sServerMethod": "POST",
        "columnDefs": [
            formatDates(data.jqueryDataTable.aoColumns),
            createCompositeColumn(data.jqueryDataTable.aoColumns,
                'col-title', 'col-author', ['col-title--author'],
                ['clickableTitle', 'titleFlex_Name', 'accFont']),
            updateSingleColumn(data.jqueryDataTable.aoColumns,
                'col-version--isbn', ['clickableVersion']),
            createCompositeColumn(data.jqueryDataTable.aoColumns,
                'col-imprint', 'col-format', ['col-imprint--format']),
            createCompositeColumn(data.jqueryDataTable.aoColumns,
                'col-lastSold', 'col-lastRecd', ['col-lastSold--lastRecd'])
        ]
    });

    new $.fn.dataTable.FixedHeader(oTable);

    EdelweissAnalytics.addFamilyGridColumnTooltips(grid);

    $("#titleGrid_wrapper .list_header")
        .load("/GetTreelineControl.aspx?controlName=/uc/listviews/menus/ListView_TopMenu.ascx&ResultType=" + lane.resultType
            + "&doHideSortMenuOption=true");

    //TODO: If Sort By Menu shows for this table, modifications are needed for the sort options to keep it consistent with the table columns
    var sortDir = getSortDirFromString(data.sortDirection);
    var sortItem = getSortItem(data.sortColumnName, lane.dashType);
    setSortDir(sortDir);
    EdelweissAnalytics.setSortOrd(sortItem);

    EdelweissAnalytics.addHeaderSortClickListener();
    EdelweissAnalytics.setHeaderSortIcon(sortItem, sortDir);

    callback(null, "Title Grid Initialization Complete");
}

EdelweissAnalytics.addFamilyGridColumnTooltips = function ($grid) {
    // market
    if (EdelweissAnalytics.doUseRetailView) {
        $grid.find("th.col-sold").attr("title", getRes("percent_of_peers_sold")).tooltip();
        $grid.find("th.col-ordered").attr("title", getRes("percent_of_peers_on_order")).tooltip();
        $grid.find("th.col-owned").attr("title", getRes("percent_of_peers_stocked")).tooltip();
    } else {
        $grid.find("th.col-sold").attr("title", getRes("percent_of_peers_circulated")).tooltip();
        $grid.find("th.col-ordered").attr("title", getRes("percent_of_peers_on_order")).tooltip();
        $grid.find("th.col-owned").attr("title", getRes("percent_of_peers_stocked")).tooltip();
    }

    // biblio
    $grid.find("th.col-version--isbn").attr("title", getRes("versions_published_within_family")).tooltip();
    $grid.find("th.col-imprint--format").attr("title", getRes("top_performing_version_within_family")).tooltip();
    $grid.find("th.col-firstPubDate").attr("title", getRes("earliest_publication_date_for_any_family_version")).tooltip();

    // location
    $grid.find("th.col-activity").attr("title", getRes("shelf_days_takes_into_account_number_copies_and_dsla")).tooltip();
    $grid.find("th.col-oo").attr("title", "On Order").tooltip();
    if (EdelweissAnalytics.doUseRetailView) {
        $grid.find("th.col-sales").attr("title", getRes("sales_at_selected_location_given_time_frame")).tooltip();
        $grid.find("th.col-oh").attr("title", getRes("current_on_hand")).tooltip();
        $grid.find("th.col-lastSold--lastRecd").attr("title", getRes("last_sold_date") + "<br>" + getRes("last_received_date")).tooltip();
    } else {
        $grid.find("th.col-sales").attr("title", getRes("circs_at_selected_location_given_time_frame")).tooltip();
        $grid.find("th.col-oh").attr("title", getRes("current_holdings")).tooltip();
        $grid.find("th.col-lastSold--lastRecd").attr("title", getRes("last_circed_date") + "<br>" + getRes("last_received_date")).tooltip();
    }

    // other
    $grid.find(".itemAllCheck").attr("title", "Select All").tooltip();
    $grid.find(".itemAllUnCheck").attr("title", "Un-Select All").tooltip();
};

EdelweissAnalytics.addGridColumnTooltips = function ($grid) {
    // market
    if (EdelweissAnalytics.doUseRetailView) {
        $grid.find("th.col-sold").attr("title", getRes('percent_of_peers_sold') + '<br>' + getRes('total_units_sold')).tooltip();
        $grid.find("th.col-ordered").attr("title", getRes('percent_of_peers_on_order') + '<br>' + getRes('total_units_ordered') + ' / ' + getRes('average_units_ordered')).tooltip();
        $grid.find("th.col-owned").attr("title", getRes('percent_of_peers_stocked') + '<br>' + getRes('total_units_stocked')).tooltip();
    } else {
        $grid.find("th.col-sold").attr("title", getRes('percent_of_peers_circulated') + '<br>' + getRes('total_items_circd')).tooltip();
        $grid.find("th.col-ordered").attr("title", getRes('percent_of_peers_on_order') + '<br>' + getRes('total_items_ordered') + ' / ' + getRes('average_units_ordered')).tooltip();
        $grid.find("th.col-owned").attr("title", getRes('percent_of_peers_owned') + '<br>' + getRes('total_items_owned')).tooltip();
    }

    // biblio

    // location
    $grid.find("th.col-circulationIndex").attr("title", getRes("shelf_days_takes_into_account_number_copies_and_dsla")).tooltip();
    $grid.find("th.col-oo--ooGapFromAve").attr("title", getRes('current_on_order') + '<br>' + getRes('gap_from_average')).tooltip();
    if (EdelweissAnalytics.doUseRetailView) {
        $grid.find("th.col-sales").attr("title", getRes('sales_at_selected_location_given_time_frame')).tooltip();
        $grid.find("th.col-oh").attr("title", getRes('current_on_hand')).tooltip();
        $grid.find("th.col-lastSold--lastRecd").attr("title", getRes('date_last_sold') + '<br>' + getRes('date_last_received')).tooltip();
        $grid.find("th.col-posCategory").attr("title", getRes('point_of_sale_category')).tooltip();
    } else {
        $grid.find("th.col-hr").attr("title", getRes('holds_ratio')).tooltip();
        $grid.find("th.col-sales").attr("title", getRes('circs_at_selected_location_given_time_frame')).tooltip();
        $grid.find("th.col-oh").attr("title", getRes('current_holdings')).tooltip();
        $grid.find("th.col-lastSold--lastRecd").attr("title", getRes('date_last_circed') + '<br>' + getRes('date_last_received')).tooltip();
        $grid.find("th.col-callNumber").attr("title", getRes('call_number')).tooltip();
    }

    // other
    $grid.find(".itemAllCheck").attr("title", getRes('select_all')).tooltip();
    $grid.find(".itemAllUnCheck").attr("title", getRes('unselect_all')).tooltip();
    $grid.find("th.col-actions").attr("title", [getRes('collections'), getRes('reviews'), getRes('shelves'), getRes('notes'), getRes('tags')].join(',')).tooltip();
};

EdelweissAnalytics.updateChartHeadersFootersByLaneKey = function (dashType, resultType, laneKey, chartIndex,
    header, primaryMessage, secondaryMessage) {

    if (IsOnAnalyticsHome(dashType)) {
        $("#headerNum" + laneKey + "_" + chartIndex).html(secondaryMessage);
        $("#chartName_" + laneKey + "_" + chartIndex).html(primaryMessage);
        $("#chartName_" + laneKey + "_" + chartIndex).show();
        if ($("#drop_" + dashType).length > 0) {
            $("#drop_" + dashType).hide();
        }
    } else {
        $("#headerNum" + laneKey + "_" + chartIndex).html(header);
        $("#chartName_" + laneKey + "_" + chartIndex).show();
        $("#footerNum" + laneKey + "_" + chartIndex).html(primaryMessage);
        $("#footerNum" + laneKey + "_" + chartIndex).show();
        if ($("#drop_" + dashType).length > 0) {
            $("#drop_" + dashType).show();
        }
    }
}

EdelweissAnalytics.updateChartHeadersFooters = function (dashType, resultType,
    primaryMessage, secondaryMessage, tertiaryMessage) {

    if ($("#dash_ResultHeader_" + resultType).length) {
        //Analytics Dashboard View
        refreshAnalyticHeader(resultType);
        $("#headerNum" + dashType).html(tertiaryMessage);
        $("#chartName_" + dashType).html(secondaryMessage);
        $("#chartName_" + dashType).show();
        if ($("#drop_" + dashType).length > 0) {
            $("#drop_" + dashType).hide();
        }
        $("#chart_" + dashType).css("margin-top", "10px");
    } else {
        //Edelweiss Dashboard View
        $("#headerNum" + dashType).html(primaryMessage);
        $("#chartName_" + dashType).show();
        $("#footerNum" + dashType).html(secondaryMessage);
        $("#footerNum" + dashType).show();
        if ($("#drop_" + dashType).length > 0) {
            $("#drop_" + dashType).show();
        }
    }
}

function GoToIPageDetail(ean) {
    window.open("https://ipage.ingramcontent.com/ipage/servlet/ibg.common.titledetail.pd1000?ean_id=" + ean, "_blank", "location=yes,height=570,scrollbars=yes,status=yes");
}

// This function returns a column def that tells the Jquery Datatable to:
// Create a column with two-valued cells, where:
//      The top value comes from the column with the class 'topColumnClass'
//      The bottom value comes from the column with the class 'bottomColumnClass'
//  And, the values are combined into the colunms with classes specified in the 
// 'compositeColumnClasses' list

// 'width' specifies the fixed width of the cells (ex: '250px')
// 'topValueCssClasses' is a list of classes that will be applied to the div containing the top value
// 'bottomValueCssClasses' is a list of classes that will be applied to the div containing the bottom value
var createCompositeColumn = function (columns,
    topColumnClass, bottomColumnClass, compositeColumnClasses,
    topValueCssClasses, bottomValueCssClasses, width) {

    if (typeof topValueCssClasses === "undefined" || topValueCssClasses === null) {
        topValueCssClasses = [];
    }
    if (typeof bottomValueCssClasses === "undefined" || bottomValueCssClasses === null) {
        bottomValueCssClasses = [];
    }
    if (typeof width === "undefined" || width === null) {
        width = "";
    }

    return {
        "render": function (data, type, row) {
            var topValueColumnIndex = getColumnIndices(columns, null, null, [topColumnClass])[0];
            if (typeof topValueColumnIndex === "undefined") {
                // col doesn't exist, do nothing
                return data;
            }

            var bottomValueColumnIndex = getColumnIndices(columns, null, null, [bottomColumnClass])[0];
            if (typeof bottomValueColumnIndex === "undefined" && typeof topValueColumnIndex === "undefined") {
                // if both cols don't exist, do nothing
                return data;
            }

            var topValue = getHtmlDisplayValue(row[topValueColumnIndex], columns[topValueColumnIndex]);
            var bottomValue = getHtmlDisplayValue(row[bottomValueColumnIndex], columns[bottomValueColumnIndex]);

            // 1-off case to deal with not repeating the same sku twice
            if (topColumnClass === "col-isbn13" && bottomColumnClass === "col-isbn10") {
                if (topValue === bottomValue) {
                    bottomValue = "&nbsp;"
                }
            }

            // for POS categories
            var hasMultiPOSValues = false;
            var descriptionArray = [];
            if (topColumnClass === "col-posCategory") {
                descriptionArray = topValue.split(",");
                if (descriptionArray.length > 1) {
                    hasMultiPOSValues = true;
                }
            }

            var compositeHtml = "<div class='compositeCell' style='width: " + width + ";'>";
            if (hasMultiPOSValues) {
                for (var i = 0; i < descriptionArray.length; i++) {
                    compositeHtml += "<div class='" + topValueCssClasses.join(' ') + "'>" + descriptionArray[i] + "</div>";
                }
            } else {
                compositeHtml += "<div class='" + topValueCssClasses.join(' ') + "'>" + topValue + "</div>";
            }
            compositeHtml += "<div class='" + bottomValueCssClasses.join(' ') + "'>" + bottomValue + "</div></div>";

            return compositeHtml;
        },
        "targets": getColumnIndices(columns, null, null, compositeColumnClasses)
    }
};

function ConstructHeaderGroupingHtml(headerGrouping) {
    var result = null;
    if (headerGrouping != null) {
        result = "<tr id='headerGrouping'>";
        for (i = 0; i < headerGrouping.length; i++) {
            result += "<th style='font-weight: bold;' class='";
            if (headerGrouping[i].sClass !== "") {
                result += headerGrouping[i].sClass;
            }
            result += "' colspan='" + headerGrouping[i].span + "'>" + headerGrouping[i].name + "</th>";
        }
        result += "</tr>";
    }
    return result;
}

var createTripledValuedColumn = function (columns,
    topColumnClass, bottomLeftColumnClass, bottomRightColumnClass,
    compositeColumnClasses, width) {

    return {
        "render": function (data, type, row) {
            var topValueColumnIndex = getColumnIndices(columns, null, null, [topColumnClass])[0];
            if (typeof topValueColumnIndex === "undefined")
                // col doesn't exist, do nothing
                return data;

            var bottomLeftValueColumnIndex = getColumnIndices(columns, null, null, [bottomLeftColumnClass])[0];
            if (typeof bottomLeftValueColumnIndex === "undefined")
                // col doesn't exist, do nothing
                return data;

            var bottomRightValueColumnIndex = getColumnIndices(columns, null, null, [bottomRightColumnClass])[0];
            if (typeof bottomRightValueColumnIndex === "undefined")
                // col doesn't exist, do nothing
                return data;

            var topValue = getHtmlDisplayValue(row[topValueColumnIndex], columns[topValueColumnIndex]);
            var bottomLeftValue = getHtmlDisplayValue(row[bottomLeftValueColumnIndex],
                columns[bottomLeftValueColumnIndex]);
            var bottomRightValue = getHtmlDisplayValue(row[bottomRightValueColumnIndex],
                columns[bottomRightValueColumnIndex]);

            var compositeHtml =
                "<div style='width: " + width + ";'>"
                + "<div>" + topValue + "</div><div>"
                + "<span>" + bottomLeftValue + "</span>&nbsp;/&nbsp;"
                + "<span>" + bottomRightValue + "</span>"
                + "</div></div>";
            return compositeHtml;
        },
        "targets": getColumnIndices(columns, null, null, compositeColumnClasses)
    }
};

var updateSingleColumn = function (columns, columnClass, valueCssClasses) {

    return {
        "render": function (data, type, row) {
            var columnIndex = getColumnIndices(columns, null, null, [columnClass])[0];
            if (typeof columnIndex === "undefined")
                // col doesn't exist, do nothing
                return data;

            var columnValue = getHtmlDisplayValue(row[columnIndex], columns[columnIndex]);
            var showCollapseButton = false;
            if (columnClass === "col-version--isbn") {
                skuList = JSON.parse(columnValue);
                if (Array.isArray(skuList)) {
                    columnValue = skuList.length + " version";
                    if (skuList.length > 1) {
                        columnValue += "s";
                    }
                    showCollapseButton = true;
                }
            }

            var singleHtml = "<div class='" + valueCssClasses.join(' ') + "'>";
            singleHtml += "<div>" + columnValue + "</div>";
            if (showCollapseButton) {
                singleHtml += "<br>";
                singleHtml += "<span class='icon-drop-down-icon iconDropDown inlineArrow availableVersionsIcon' style='margin-left: 15px;'></span>";
            }
            singleHtml += "</div>";
            return singleHtml;
        },
        "targets": getColumnIndices(columns, null, null, [columnClass])
    }
};

function getHtmlDisplayValue(value, columnOfValue) {
    if (typeof value !== "undefined" && value !== null) {
        if (columnOfValue.sType === 'date') {
            return getFormattedDateUsingLocale(value);
        } else {
            return value;
        }
    } else {
        return "&nbsp;";
    }
};

function getFormattedDateUsingLocale(data) {
    if (typeof data !== "undefined" && data !== null) {
        var date = new Date(data.replace(/-/g, '/'));
        return date.toLocaleString(window.cultureName,
            { year: "2-digit", month: "2-digit", day: "2-digit" });
    } else {
        return "&nbsp;";
    }
};

var formatDates = function (columns) {
    return {
        "render": function (data, type, row) {
            return getFormattedDateUsingLocale(data);
        },
        "targets": getColumnIndices(columns, [], 'date')
    }
};

EdelweissAnalytics.formatNumbers = function (columns) {
    return {
        "render": function (data, type, row) {
            return getFormattedNumbersUsingLocale(data);
        },
        "targets": getColumnIndices(columns, [], 'number')
    }
};

function getFormattedNumbersUsingLocale(data) {
    if (typeof data !== "undefined" && data !== null) {
        var fData = parseFloat(data);
        return fData.toLocaleString(window.cultureName);
    } else {
        return "&nbsp;";
    }
}

function getCurrencyFormattedNumbersUsingLocale(data) {
    if (typeof data !== "undefined" && data !== null) {
        var fData = parseFloat(data);
        var currencyCode = 'USD';
        if (window.cultureName === "en-GB") {
            currencyCode = 'GBP';
        } else if (window.cultureName === "fr-FR" || window.cultureName === "de-DE") {
            currencyCode = 'EUR';
        }
        return fData.toLocaleString(window.cultureName, { style: 'currency', currency: currencyCode });
    } else {
        return "&nbsp;";
    }
}

window.EdelweissAnalytics.getPercentFormattedNumbersUsingLocale = function (value) {
    return value.toLocaleString(
        window.cultureName, { style: 'percent', maximumFractionDigits: 5 });
};

// This function chooses a subset of columns from a list of columns.
// Each column should be a JSON object with sType and sTitle objects.
// The values are specified in BaseTitleListAnalysis.vb > GetTableColumnOptions where
// the 'type' attribute maps to sType and the 'label' attribute maps to sTitle.
// If 'columnNames' is specified, check for a matching sTitle in each column
// If 'type' is specified, check for a matching sType in each column.
// If 'className' is specified, check for a matching sClass in each column
function getColumnIndices(columns, columnNames, type, classNames) {
    type = type || null;
    classNames = classNames || [];
    columnNames = columnNames || [];

    var columnIndices = [];
    columns.forEach(function (column, index) {
        var mainClass = column.sClass.split(" ")[0];
        if ((type === null || column.sType === type)
            && (columnNames.length === 0 || columnNames.indexOf(column.sTitle) != -1)
            && (classNames.length === 0 || classNames.indexOf(mainClass) != -1)) {
            columnIndices.push(index);
        }
    });
    return columnIndices;
}

function InitializePerformanceGrid(oSettings, grid) {
    grid.children("tbody").children("tr").children("td.col-status:contains('Understocked')").css("color", "#dac912");
    grid.children("tbody").children("tr").children("td.col-status:contains('Understocked')").css("font-weight", "bold");
    grid.children("tbody").children("tr").children("td.col-status:contains('Overstocked')").css("color", "#EB4A18");
    grid.children("tbody").children("tr").children("td.col-status:contains('Overstocked')").css("font-weight", "bold");

    var generalPercentText = getRes('percent_of_total');
    $(".col--total-sales:not(td)").html(generalPercentText);
    $(".col--total-unit-sales:not(td)").html(generalPercentText);
    $(".col--total-inventory:not(td)").html(generalPercentText);
    $(".col--total-unit-inventory:not(td)").html(generalPercentText);

    $(".col-category:not(th)").css("cursor", "pointer");
    $(".col-category:not(th)").off().on("click", function (event) {
        ViewSubCategoriesDetail(event);
        event.stopPropagation();
    });

    $(".col-location:not(th)").css("cursor", "pointer");
    $(".col-location:not(th)").off().on("click", function (event) {
        var tdElement = $(event.target).parent();
        var table = grid.DataTable();
        var rowData = table.row(tdElement).data();
        var locationIdColumnIndex = table.column("Location Id:name").index();
        var locationId = rowData[locationIdColumnIndex];
        EdelweissAnalytics.savePeerBranchAndUpdateLane(locationId);
        event.stopPropagation();
    });
}

function InitializeTitleGridInISBNView(oSettings, grid) {
    $(".col-isbn:not(th)")
        .off()
        .on("click", function (event) {
            var sel = getSelection().toString();
            if (!sel) {
                ViewOpac(event);
                event.stopPropagation();
            }
        });

    $(".clickableTitle")
        .off()
        .on("click", function (event) {
            var sel = getSelection().toString();
            if (!sel) {
                var tdElement = $(event.target).parent().parent();
                ViewProductDetailInISBNView(tdElement);
                event.stopPropagation();
            }
        });

    $(".col-badges:not(th)").each(function (index) {
        var sku = $(this).parent().find("td.col-isbn div.rowSku").text();
        var badgesContainer = $(this).children();
        $(badgesContainer).load("/GetTreelineControl.aspx?controlName=/uc/controls/ActionStrip_Titles_Simple.ascx&sku=" + sku + "&actionItemClass=simpleAction");
    });

    $(".tlList").each(function (index) {
        // When there is no data to be displayed, jQuery dataTables adds a row
        // with class tlList that says "No Matching Records Found" and does not
        // contain the usual itemCheck element with in it. We must check for this.
        var itemCheckElement = $(this).find(".itemCheck");
        if (itemCheckElement.length !== 0) {
            var sku = itemCheckElement.attr("id").replace("check_", "");
            $(this).attr("id", "lv_" + sku);
        }
    });

    EdelweissAnalytics.setStateOfSelectAllTitlesOnCurrentPageButton();
    EdelweissAnalytics.setStateOfSelectAllTitlesOnAllPagesButton();

    if (EdelweissAnalytics.isLibrary) {
        addIlsCopyDetailIcon(false);
    }
}

function InitializeTitleGridInFamilyView(oSettings, grid, dashType) {
    /** Styling each market percentage data with pie chart shading **/
    EdelweissAnalytics.stylePercentageData(grid);

    $(".clickableTitle")
        .off()
        .on("click", function (event) {
            var tdElement = $(event.target).parent().parent();
            ViewProductDetailInFamilyView(tdElement);
            event.stopPropagation();
        });

    $(".tlList").each(function (index) {
        // When there is no data to be displayed, jQuery dataTables adds a row
        // with class tlList that says "No Matching Records Found" and does not
        // contain the usual itemCheck element with in it. We must check for this.
        var itemCheckElement = $(this).find(".itemCheck");
        if (itemCheckElement.length !== 0) {
            var sku = itemCheckElement.attr("id").replace("check_", "");
            $(this).attr("id", "lv_" + sku);
        }
    });

    EdelweissAnalytics.addClickableVersionListener(grid,
        EdelweissAnalytics.filterOptions[dashType].locationFilters,
        EdelweissAnalytics.filterOptions[dashType].monthsBack
    );

    EdelweissAnalytics.setStateOfSelectAllTitlesOnCurrentPageButton();
    EdelweissAnalytics.setStateOfSelectAllTitlesOnAllPagesButton();

    if (!EdelweissAnalytics.doUseRetailView) {
        addIlsCopyDetailIcon(true);
    }
}

EdelweissAnalytics.addClickableVersionListener = function (grid, locations, monthsBack, isSeriesView) {
    $(".clickableVersion").off().on("click", function (event) {
        EdelweissAnalytics.onVersionsDropdownClick($(this), grid, locations, monthsBack, isSeriesView);
    });
};

EdelweissAnalytics.onVersionsDropdownClick = function ($clickableVersionIcon, grid, locations, monthsBack, isSeriesView) {
    var trElement = $clickableVersionIcon.closest('tr');
    var table = grid.DataTable();
    var row = table.row(trElement);
    var data = row.data();
    var skuList = data[table.column("Versions:name").index()];
    skuList = JSON.parse(skuList);

    var iconElement = $clickableVersionIcon.find(".availableVersionsIcon");

    if (row.child.isShown()) {
        row.child.hide();
        trElement.removeClass("shown");
        if (iconElement.hasClass("icon-drop-up-icon-01")) {
            iconElement.removeClass("icon-drop-up-icon-01");
            iconElement.removeClass("activeDropupIcon");
            iconElement.addClass("icon-drop-down-icon");
        }
    }
    else {
        EdelweissAnalytics.formatDetails(row.child, skuList, locations, monthsBack, isSeriesView);
        trElement.addClass("shown");
        if (iconElement.hasClass("icon-drop-down-icon")) {
            iconElement.removeClass("icon-drop-down-icon");
            iconElement.addClass("icon-drop-up-icon-01");
            iconElement.addClass("activeDropupIcon");
        }
    }
    event.stopPropagation();
};

function addIlsCopyDetailIcon(isFamilyGrid) {
    $(".col-oh:not(th)").each(function (index) {
        if (isFamilyGrid) {
            var columnName = "TopPerformingSku";
        } else {
            var columnName = "ISBN10";
        }

        var table = $("#titleGrid").DataTable();
        var rowData = table.row($(this).parent()).data();
        var sku = rowData[table.column(columnName + ":name").index()];

        if (typeof sku === "undefined" || sku === null || sku.length === 0) {
            console.warn("Unable to build copy detail link.  SKU not found.");
            return;
        }
        var startingContent = "40"; // ILS Copy Detail
        var source = "dash";
        var ilsCopyDetailIconHtml = "<span class='ilsCopyDetailIcon icon-list-select iconSVG_Darker'"
            + " onclick=\"loadModalTitle('" + sku + "', '" + source + "', " + startingContent + ");\""
            + " style='font-size:14px; vertical-align:middle; margin-left:4px;'></span>"
        $(this).css("padding-left", "12px");
        $(this).css("position", "relative");
        $(this).append(ilsCopyDetailIconHtml);
    });
}

function InitializeChildTitlesGrid(oSettings, sku, data, isSeriesView) {
    if (data.hasOwnProperty("headerGrouping")) {
        $("#childDetail_" + sku).find("#headerGrouping").remove();
        var headerGrouping = data.headerGrouping;
        var headerGroupingHtml = ConstructHeaderGroupingHtml(headerGrouping);
        if (headerGroupingHtml != null) {
            $("#childDetail_" + sku).children("thead").prepend(headerGroupingHtml);
        }
    }

    $("#childDetail_" + sku + "_wrapper").addClass("childDtail_Wrapper");
    if (isSeriesView) {
        $("#childDetail_" + sku + "_wrapper").addClass("childDetail_reducedHorizontalMargins");
    }

    var $titleColTdElements = $(".col-title:not(th)");
    $titleColTdElements.addClass("clickableTitle titleFlex_Name accFont");
    $titleColTdElements.off().on("click", function (event) {
        var tdElement = $(event.target);
        ViewProductDetailInChildTable(tdElement, sku);
        window.closeSeriesData();
        event.stopPropagation();
    });

    $("#childDetail_" + sku).find(".col-badges:not(th)").each(function (index) {
        var ean = $(this).parent().find("td.col-isbn div.rowSku").text();
        var badgesContainer = $(this).children();
        $(badgesContainer).load("/GetTreelineControl.aspx?controlName=/uc/controls/ActionStrip_Titles_Simple.ascx&sku=" + ean + "&actionItemClass=simpleAction");
    });

    /** Styling each market percentage data with pie chart shading **/
    var childGrid = $("#childDetail_" + sku);
    EdelweissAnalytics.stylePercentageData(childGrid);

    if (isSeriesView) {
        EdelweissAnalytics.Series.addTooltipHoverToShelfDayFlamesInFamilyDetailsView(sku);
    }
}

// In order to use 1.10+ jQuery DataTable API functions, use DataTable() to call 1.10+ table
function ViewSubCategoriesDetail(event) {
    var tdElement = $(event.target).parent();
    var table = $("#performanceGrid").DataTable();
    var rowData = table.row(tdElement).data();
    var subCategoriesCount = rowData[table.column("SubcategoryCount:name").index()];
    var categoryCode = rowData[table.column("CategoryCode:name").index()];
    var categoryName = $(event.target).text();
    var isLeafCategory = isNaN(subCategoriesCount) || parseInt(subCategoriesCount) === 0;
    EdelweissAnalytics.drillDownCategory(categoryCode, categoryName, isLeafCategory);
}

function ViewOpac(event) {
    var row = $(event.target).parent().parent();
    var data = oTable.fnGetData(row);

    for (var i = 0; i < data.length; i++) {
        if (typeof data[i] == "string" && data[i].indexOf("opacLink") > 0) {
            var url = $(data[i]).attr("href");
            var win = window.open(url, "_blank");

            win.focus();
        }
    }
}

function ViewProductDetailInISBNView(tdElement) {
    var sku = tdElement.parent().find("td.col-isbn div.rowSku").text();
    loadModalTitle(sku, 'dash', 0);
}


function ViewProductDetailInFamilyView(tdElement) {
    var table = $("#titleGrid").DataTable();
    var rowData = table.row(tdElement.parent()).data();
    var sku = rowData[table.column("TopPerformingSku:name").index()];
    loadModalTitle(sku, 'dash', 0);
}

function ViewProductDetailInChildTable(tdElement, sku) {
    var table = $("#childDetail_" + sku).DataTable();
    var rowData = table.row(tdElement.parent()).data();
    var childSku = rowData[table.column("ISBN10:name").index()];
    closeModal();
    loadModalTitle(childSku, 'dash', 0);
}

function addSimpleActionAnalytics(suffix) {

    var parent = $("#simpleAction_" + suffix).parent();

    addSimpleActionPopup(parent, suffix);

    if ($("#addSimple_" + suffix).css("display") === "none") {
        $(".addSimple").hide();
        showSimpleAction(suffix);
    } else {
        hideSimpleAction(suffix);
    }
}

function addSimpleActionPopup(parentElement, suffix) {

    if ($("#addSimple_" + suffix).length <= 0) {

        var simpleActionPopup = document.createElement("div");
        simpleActionPopup.id = "addSimple_" + suffix;
        simpleActionPopup.className = "addSimple";
        simpleActionPopup.style = "position: absolute; right: 80px; background-color: #ffffff; top: -5px; display: none; border: 1px solid #d2d6d8; z-index: 80;";

        var containerDiv = document.createElement("div");
        containerDiv.style = "margin-top: 10px; margin-bottom: 10px; margin-right: 10px;";

        var action1 = document.createElement("div");
        action1.id = "action1";
        action1.className = "columnSpaced stripItem icon-add-to-collection-icon iconSVG";
        action1.addEventListener("click", function () { openAddtoCollection(suffix) });

        var action2 = document.createElement("div");
        action2.id = "action2";
        action2.className = "columnSpaced stripItem icon-review-icon iconSVG";
        action2.addEventListener("click", function () { openTurboReview(suffix) });

        var action3 = document.createElement("div");
        action3.id = "action3";
        action3.className = "columnSpaced stripItem icon-bookkshelf-icon iconSVG";
        action3.addEventListener("click", function () { openShelveTitle(suffix) });

        var action4 = document.createElement("div");
        action4.id = "action4";
        action4.className = "columnSpaced stripItem icon-note-icon iconSVG";
        action4.addEventListener("click", function () { openPersonalNote(suffix) });

        var action5 = document.createElement("div");
        action5.id = "action5";
        action5.className = "columnSpaced stripItem icon-tag-icon iconSVG";
        action5.addEventListener("click", function () { openPersonalTags(suffix) });

        var clearDiv = document.createElement("div");
        clearDiv.style = "clear: both;";

        containerDiv.appendChild(action1);
        containerDiv.appendChild(action2);
        containerDiv.appendChild(action3);
        containerDiv.appendChild(action4);
        containerDiv.appendChild(action5);
        containerDiv.appendChild(clearDiv);

        simpleActionPopup.appendChild(containerDiv);

        $(parentElement).append(simpleActionPopup);
    }
}

EdelweissAnalytics.formatDetails = function (callback, skus, locations, monthsBack, isSeriesView) {
    var getDataPromise = EdelweissAnalytics.getHoldingsDataForSkus(skus, locations, monthsBack, isSeriesView);
    getDataPromise.then(function (data) {
        var dHtml = "<table id='childDetail_" + skus[0] + "' class= 'childrenTable'><thead><tr></tr></thead></table>";
        callback($('<div>' + dHtml + '</div>')).show();
        cTable = null;
        var childGrid = $("#childDetail_" + skus[0]);
        cTable = childGrid.dataTable({
            language: getDataTableLocalization(),
            "aaSorting": [], // no initial client-side sorting
            "dom": "tp",
            "destroy": true,
            "bAutoWidth": false,
            "columns": data.aoColumns,
            "data": data.aaData,
            "pageLength": 5,
            "fnDrawCallback": function (oSettings) {
                InitializeChildTitlesGrid(oSettings, skus[0], data, isSeriesView);
            },
            "columnDefs": [
                formatDates(data.aoColumns),
                createCompositeColumn(data.aoColumns, 'col-isbn13', 'col-isbn10', ['col-isbn'], ['rowSku']),
                createCompositeColumn(data.aoColumns, 'col-pubDate', 'col-price', ['col-pubDate--price']),
                createCompositeColumn(data.aoColumns, 'col-imprint', 'col-format', ['col-publisher--format']),
                createCompositeColumn(data.aoColumns, 'col-lastSold', 'col-lastRecd', ['col-lastSold--lastRecd'])
            ]
        });
    });
}

EdelweissAnalytics.getHoldingsDataForSkus = function (skus, locations, monthsBack, isSeriesView) {
    var params = new FilterOption();
    params.productIds = skus;
    params.numberRequested = skus.length;
    params.locationFilters = locations;
    params.referenceCode = isSeriesView ?
        EdelweissAnalytics.referenceCodes.FamilyDetailsViewInSeriesView :
        EdelweissAnalytics.referenceCodes.FamilyDetailsView;
    params.monthsBack = monthsBack;
    params.segmentationMode = EdelweissAnalytics.segmentationModes.Activity;

    var dataApiUrl = "api/v3/analysis/stock/holdingsTable";

    var getDataPromise = new Promise(function (resolve, reject) {
        $.ajax({
            type: "POST",
            data: params,
            url: dataApiUrl,
            success: function (data) {
                resolve(data);
            },
            error: function () {
                alert("There was an error retrieving the data for the skus in this family.");
            },
            datatype: "json"
        });
    });
    return getDataPromise;
}

EdelweissAnalytics.changeDataTableSort = function (sortItem) {
    setSortOrdIcon(sortItem);
    EdelweissAnalytics.setSortOrd(sortItem);
    sortDataTable();
}

EdelweissAnalytics.changeDataTableSortOnTableHeaderClick = function (sortItem) {
    if (getSortOrd() == sortItem) {
        var sortDir = getSortDir() == 0 ? 1 : 0;
    } else {
        var sortDir = 1;
    }
    setSortDir(sortDir);
    EdelweissAnalytics.setSortOrd(sortItem);
    EdelweissAnalytics.setHeaderSortIcon(sortItem, sortDir);
    sortDataTable();
};

EdelweissAnalytics.addHeaderSortClickListener = function () {
    $(".analyticsGridSortableLabel").on("click", function () {
        var sortItem = $(this).attr("data-sortItem");
        EdelweissAnalytics.changeDataTableSortOnTableHeaderClick(sortItem);
    });
};

EdelweissAnalytics.setHeaderSortIcon = function (sortItem, sortDirection) {
    $(".analyticsGridSortableLabel").removeClass("sortAscendingIcon");
    $(".analyticsGridSortableLabel").removeClass("sortDescendingIcon");
    var $elem = $(".analyticsGridSortableLabel[data-sortItem='" + sortItem + "'");
    if (sortDirection === 1) {
        $elem.removeClass("sortAscendingIcon");
        $elem.addClass("sortDescendingIcon");
    } else {
        $elem.removeClass("sortDescendingIcon");
        $elem.addClass("sortAscendingIcon");
    }
};

function getSortDirectionString(sortDir) {
    // non-strict equality used to allow for 1 to equal "1" and 0 to equal "0"
    return sortDir == 1 ? "desc" : "asc";
}

// This is the inverse of the getSortDirectionString() function
function getSortDirFromString(sortDirectionString) {
    return sortDirectionString === "desc" ? 1 : 0;
}

EdelweissAnalytics.hasRequiredPerformanceActivity = function (dashType) {
    return EdelweissAnalytics.filterOptions.hasOwnProperty(dashType) &&
        EdelweissAnalytics.filterOptions[dashType].requiredPerformanceActivity !== undefined &&
        EdelweissAnalytics.filterOptions[dashType].requiredPerformanceActivity !==
        getEnumValue("requiredPerformanceActivity", "NONE");
}

// These column names MUST match the labels given in BaseTitleListAnalysis.vb > GetTableOptions
EdelweissAnalytics.getColumnNames = function (dashType) {
    var isRetailerAndHasRequiredPerformanceActivity =
        EdelweissAnalytics.doUseRetailView &&
        EdelweissAnalytics.hasRequiredPerformanceActivity(dashType);

    return ["", EdelweissAnalytics.doUseRetailView ? "OH" : "CH", "OO", "Pub Date", "Title", "AI", "Last Sold", "HR",
        "Percent That Ordered", "Total Units Ordered", "Average Units Ordered", "Percent That Own",
        "Total Units Owned", "Percent That Sold", "Total Units Sold",
        isRetailerAndHasRequiredPerformanceActivity ? "Unit Sales " : "Sales ", "Circ ", "Call#", "Turn", "Author", "", "Imprint", "Format", "ISBN"]
};

EdelweissAnalytics.columnNamesForFamilyView = ["Title", "First Pub Date", "Circ", "Ordered", "Owned", "Sold", "Stocked", "Last Sold", "AI", "Author", "Imprint"];

function getColumnName(sortItem, dashType) {
    var columnNamesIndex = 0;
    var columnName = "";

    if (EdelweissAnalytics.filterOptions[dashType].referenceCode === EdelweissAnalytics.referenceCodes.FamilyView) {
        columnNamesIndex = sortItem - 230;
        columnName = EdelweissAnalytics.columnNamesForFamilyView[columnNamesIndex];
    } else {
        columnNamesIndex = sortItem - 200;
        columnName = EdelweissAnalytics.getColumnNames(dashType)[columnNamesIndex];
    }
    return columnName;
}

// This is the inverse of the getColumnName() function
function getSortItem(columnName, dashType) {
    var columnNamesIndex = 0;
    if (EdelweissAnalytics.filterOptions[dashType].referenceCode === EdelweissAnalytics.referenceCodes.FamilyView) {
        columnNamesIndex = EdelweissAnalytics.columnNamesForFamilyView.indexOf(columnName);
    } else {
        columnNamesIndex = EdelweissAnalytics.getColumnNames(dashType).indexOf(columnName);
    }
    if (columnNamesIndex < 0) {
        console.warn("A sort item was not found for this column name");
        columnNamesIndex = 0;
    }
    var sortItem = 0;
    if (EdelweissAnalytics.filterOptions[dashType].referenceCode === EdelweissAnalytics.referenceCodes.FamilyView) {
        sortItem = columnNamesIndex + 230;
    } else {
        sortItem = columnNamesIndex + 200;
    }
    return sortItem;
}

function getColumnIndex(table, columnName) {
    var columnIndex = table.column(columnName + ":name").index();
    if (columnIndex === undefined) {
        alert("Cannot find the selected column in the datatable!");
        columnIndex = 0;
    }
    return columnIndex;
}

function sortDataTable() {
    var sortDir = getSortDir();
    var sortItem = getSortOrd();
    var directionString = getSortDirectionString(sortDir);
    var columnName = "";

    dashTypesOfAnalyticsLanes.forEach(function (dashType) {
        var $dash = $("#dash_" + dashType);
        if ($dash.is(":visible")) {
            columnName = getColumnName(sortItem, dashType);
            // store this custom sort for this lane/wedge
            if (EdelweissAnalytics.filterOptions[dashType].includeTitlesFromAllStockAnalysisClasses) {
                EdelweissAnalytics.sortColumnNameForAllTitles[dashType] = columnName;
                EdelweissAnalytics.sortDirectionForAllTitles[dashType] = directionString;
                // deselect all titles during changing the sort options 
                //since the stored sort options passing to the server-side only happens in the function of selectAllTitlesOnAllPages.                
                if ($("#ea_selectAllTitlesOnAllPages").hasClass("checkmark_checked")) {
                    EdelweissAnalytics.deselectAllTitlesOnAllPages();
                }

            } else {
                var stockAnalysisClass = EdelweissAnalytics.filterOptions[dashType].stockAnalysisClass;
                EdelweissAnalytics.sortColumnNameByStockAnalysisClass[dashType][stockAnalysisClass] = columnName;
                EdelweissAnalytics.sortDirectionByStockAnalysisClass[dashType][stockAnalysisClass] = directionString;
            }
            EdelweissAnalytics.filterOptions[dashType].sortColumnName = columnName;
            EdelweissAnalytics.filterOptions[dashType].sortDirection = directionString;

            var laneKey = $dash.attr("data-lanekey");
            EdelweissAnalytics.getLane(laneKey).then(function (lane) {
                async.parallel([
                    async.apply(EdelweissAnalytics.saveAnalyticsUserPreference,
                        lane.search.prefSortColumn, columnName),
                    async.apply(EdelweissAnalytics.saveAnalyticsUserPreference,
                        lane.search.prefSortDirection, directionString)
                ]);
            });
        }
    });
    var table = $("#titleGrid").DataTable();
    var columnIndex = getColumnIndex(table, columnName);
    table.order([columnIndex, directionString]).draw();
}
EdelweissAnalytics.changeAscDesc = function () {
    var sortDir = getSortDir() == 0 ? 1 : 0;
    setSortDirIcon(sortDir);
    setSortDir(sortDir);
    sortDataTable();
}

EdelweissAnalytics.setSortOrd = function (value) {
    setListViewProperty("sortOrd", value);
}

EdelweissAnalytics.initializeJqueryDateRangePicker = function (fromElement, toElement,
    pubDateLowerBoundString, pubDateUpperBoundString, cultureName) {

    var options = $.datepicker.regional[cultureName];
    options["minDate"] = 0; // minimum date is today

    if (_.isEmpty(pubDateLowerBoundString)) {
        var fromDefaultDate = new Date();
    } else {
        var fromDefaultDate = new Date(pubDateLowerBoundString);
    }

    var from = $(fromElement).datepicker(options).datepicker("setDate", fromDefaultDate);
    from.on("change", function () {
        if ($("#includeUpperBoundCheckBox").prop("checked")) {
            var fromDate = $(fromElement).datepicker("getDate");
            var toDate = $(toElement).datepicker("getDate");
            if (moment(fromDate).isAfter(toDate, 'day')) {
                $(toElement).datepicker("setDate", fromDate);
            }
        }
    })

    var to = $(toElement).datepicker(options)
    to.on("change", function () {
        var fromDate = $(fromElement).datepicker("getDate");
        var toDate = $(toElement).datepicker("getDate");
        if (moment(toDate).isBefore(fromDate, 'day')) {
            $(fromElement).datepicker("setDate", toDate);
        }
    });

    if (!_.isEmpty(pubDateUpperBoundString) && pubDateUpperBoundString !== "*") {
        var toDefaultDate = new Date(pubDateUpperBoundString);
        to.datepicker("setDate", toDefaultDate);
        $("#pubDateUpperBound_wrapper").show();
        $("#includeUpperBoundCheckBox").prop("checked", true);
    }

    $("#includeUpperBoundCheckBox").click(function () {
        if ($("#includeUpperBoundCheckBox").prop("checked")) {
            var fromDate = $(fromElement).datepicker("getDate");
            $(toElement).datepicker("setDate", fromDate);
            $("#pubDateUpperBound_wrapper").show();
        } else {
            $("#pubDateUpperBound_wrapper").hide();
        }
    });
}

// A general function used to save any analytics user preference
// Can be used with an async.js callback if supplied
EdelweissAnalytics.saveAnalyticsUserPreference = function (name, value, callback) {
    callback = typeof callback === "undefined" ? null : callback;

    $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", {
        type: 'analytics',
        name: name,
        value: value
    }, function (data) {
        if (callback !== null) {
            if (data.code === "OK") {
                return callback(null, "Saved analytics user preference: {" + name + ", " + value + "}");
            } else {
                return callback(new Error("Error saving analytics user preference."));
            }
        }
    });
}

EdelweissAnalytics.saveShowAllPreference = function (laneKey, value, callback) {
    EdelweissAnalytics.saveAnalyticsUserPreference('showAll-' + laneKey, value, callback);
}

EdelweissAnalytics.getLane = function (laneKey) {
    var lanePromise = new Promise(function (resolve, reject) {
        $.get("api/v2/analytics/lanes/" + laneKey, function (lane) {
            resolve(lane);
        });
    });
    return lanePromise;
};

EdelweissAnalytics.toggleCheck = function (item) {
    var index = arrayObjectIndexOf(EdelweissAnalytics.rows, item, "item");
    var jqItem = item.replace(",", "\\,");
    if ($("#check_" + jqItem).hasClass("checkmark_checked")) {
        $("#check_" + jqItem).removeClass("checkmark_checked");
        $("#num_" + jqItem).removeClass("num_checked");
        if (index > -1) {
            EdelweissAnalytics.rows.splice(index, 1);
            EdelweissAnalytics.selected -= 1;
        }
    } else {
        $("#check_" + jqItem).addClass("checkmark_checked");
        $("#num_" + jqItem).addClass("num_checked");
        if (index > -1) {
            EdelweissAnalytics.rows[index].selected = 1;
        } else {
            EdelweissAnalytics.rows.push({ item: item, selected: 1 });
        }
        EdelweissAnalytics.selected += 1;
    }

    EdelweissAnalytics.setStateOfSelectAllTitlesOnCurrentPageButton();
    EdelweissAnalytics.setStateOfSelectAllTitlesOnAllPagesButton();
    EdelweissAnalytics.handleSelectedItems(EdelweissAnalytics.selected);
    window.getResults();
}

function arrayObjectIndexOf(myArray, searchTerm, property) {
    if (myArray.length > 0) {
        for (var i = 0; i < myArray.length; i++) {
            if (myArray[i][property] === searchTerm) return i;
        }

    }
    return -1;
}
//data: an array of array, containing row information
EdelweissAnalytics.selectAllItemsOnCurrentPage = function (isFamilyMode, isWeedingAnalysis) {
    var isFamilyModeTrue = isFamilyMode.toLowerCase() === 'true';

    var itemList = [];
    if (isWeedingAnalysis) {
        var table = $("#weedingGrid").DataTable();
        var start = table.page.info().start;
        var end = table.page.info().end;
        var data = table.rows({ order: 'applied' }).data();
        for (var i = start; i < end; i++) {
            itemList.push(data[i].selectValue);
        }
    } else {
        var table = $("#titleGrid").DataTable();
        var columnName = "";
        if (isFamilyModeTrue) {
            columnName = "TopPerformingSku";
        } else {
            columnName = "ISBN10";
        }

        var columnIndex = table.column(columnName + ":name").index();
        var data = table.data();
        for (var i = 0; i < data.length; i++) {
            itemList.push(data[i][columnIndex]);
        }
    }

    if (itemList.length > 0) {
        for (var i = 0; i < itemList.length; i++) {
            index = arrayObjectIndexOf(EdelweissAnalytics.rows, itemList[i], "item");
            if (index === -1) {
                EdelweissAnalytics.rows.push({ item: itemList[i], selected: 1 });
                EdelweissAnalytics.selected += 1;
            }
        }
    }
    $(".itemCheck").addClass("checkmark_checked");
    $(".itemRowNumber").addClass("num_checked");
    $(".checkVisual").addClass("red-stripe");

    EdelweissAnalytics.setStateOfSelectAllTitlesOnCurrentPageButton();
    EdelweissAnalytics.setStateOfSelectAllTitlesOnAllPagesButton();
    EdelweissAnalytics.handleSelectedItems(EdelweissAnalytics.selected);
    window.getResults();
}

EdelweissAnalytics.deselectAllTitlesOnAllPages = function () {
    EdelweissAnalytics.selected = 0;
    $(".itemCheck").removeClass("checkmark_checked");
    $(".itemRowNumber").removeClass("num_checked");
    EdelweissAnalytics.rows = [];

    EdelweissAnalytics.setStateOfSelectAllTitlesOnCurrentPageButton();
    EdelweissAnalytics.setStateOfSelectAllTitlesOnAllPagesButton();
    EdelweissAnalytics.handleSelectedItems(EdelweissAnalytics.selected);
    window.getResults();
}

EdelweissAnalytics.handleSelectedItems = function (selectedItems) {
    if (selectedItems > 0) {
        var str = "(";
        str += EdelweissAnalytics.selected.toLocaleString() + " Items Selected)";
        $(".ea_itemsSelected").html(str);
        $(".ea_itemsSelected").show();
    }
    if (selectedItems === 0) {
        $(".ea_itemsSelected").html();
        $(".ea_itemsSelected").hide();
    }
}

EdelweissAnalytics.setStateOfSelectAllTitlesOnAllPagesButton = function () {
    var numberOfItemsOnAllPages = EdelweissAnalytics.analysisSetSize;
    var numberOfItemsCheckedOnAllPages = EdelweissAnalytics.rows.length;
    if (numberOfItemsCheckedOnAllPages == numberOfItemsOnAllPages) {
        $("#ea_selectAllTitlesOnAllPages").addClass("checkmark_checked");
    } else {
        $("#ea_selectAllTitlesOnAllPages").removeClass("checkmark_checked");
    }
};

EdelweissAnalytics.setStateOfSelectAllTitlesOnCurrentPageButton = function () {
    var numberOfItemsOnCurrentPage = $(".itemCheck").length;
    var numberOfItemsCheckedOnCurrentPage = $(".itemCheck.checkmark_checked").length;
    if (numberOfItemsCheckedOnCurrentPage == numberOfItemsOnCurrentPage) {
        $(".itemAllCheck").addClass("checkmark_checked");
    } else {
        $(".itemAllCheck").removeClass("checkmark_checked");
    }
};

EdelweissAnalytics.getSkuListForAllTitles = function (dashType, filtersCacheKey, analysisCacheKey, stockAnalysisClass) {
    var sortColumnName = "";
    if (EdelweissAnalytics.sortColumnNameForAllTitles[dashType] !== "") {
        sortColumnName = EdelweissAnalytics.sortColumnNameForAllTitles[dashType];
    }
    var sortDirection = "";
    if (EdelweissAnalytics.sortDirectionForAllTitles[dashType] !== "") {
        sortDirection = EdelweissAnalytics.sortDirectionForAllTitles[dashType];
    }

    var stockSkuListApiUrl = "/api/analysis/stock/skuList";

    var params = new FilterOption();
    params.filtersCacheKey = filtersCacheKey;
    params.sortColumnName = sortColumnName;
    params.sortDirection = sortDirection;
    params.stockAnalysisClass = stockAnalysisClass;

    if (dashType !== getEnumValue("dashType", "EA_TRENDSANALYSIS")) {
        params.analysisCacheKey = analysisCacheKey;
    }

    var getSkuListPromise = new Promise(function (resolve, reject) {
        $.ajax({
            type: "POST",
            url: stockSkuListApiUrl,
            data: params,
            success: function (skuList) {
                resolve(skuList);
            },
            error: function () {
                alert("There was an error retrieving the list of skus.");
            },
            datatype: "json"
        });
    });
    return getSkuListPromise;
}

EdelweissAnalytics.selectAllTitlesOnAllPagesInWeedingGrid = function () {
    var itemList = [];
    var table = $("#weedingGrid").DataTable();
    var data = table.rows({ order: 'applied' }).data();
    for (var i = 0; i < data.length; i++) {
        itemList.push(data[i].selectValue);
    }

    if (itemList.length > 0) {
        for (var i = 0; i < itemList.length; i++) {
            index = arrayObjectIndexOf(EdelweissAnalytics.rows, itemList[i], "item");
            if (index === -1) {
                EdelweissAnalytics.rows.push({ item: itemList[i], selected: 1 });
                EdelweissAnalytics.selected += 1;
            }
        }
    }
    $(".itemCheck").addClass("checkmark_checked");
    $(".itemRowNumber").addClass("num_checked");
    $(".checkVisual").addClass("red-stripe");

    EdelweissAnalytics.setStateOfSelectAllTitlesOnCurrentPageButton();
    EdelweissAnalytics.setStateOfSelectAllTitlesOnAllPagesButton();
    EdelweissAnalytics.handleSelectedItems(EdelweissAnalytics.selected);
    window.getResults();
};

EdelweissAnalytics.selectAllTitlesOnAllPages = function (numTitles, dashType) {
    if (numTitles > EdelweissAnalytics.MaximumNumberOfTitlesForExport) {
        var message = "The number of titles in the chart exceeds the selectable limit of ";
        message += EdelweissAnalytics.MaximumNumberOfTitlesForExport.toLocaleString();
        message += " titles."
            + " Please filter the result to get a smaller set of data.";
        alert(message);
        return;
    }
    var skuList;
    var filtersCacheKey = EdelweissAnalytics.filterOptions[dashType].filtersCacheKey;
    var analysisCacheKey = EdelweissAnalytics.filterOptions[dashType].analysisCacheKey;
    var stockAnalysisClass = EdelweissAnalytics.filterOptions[dashType].stockAnalysisClass;

    var getSkuListPromise = EdelweissAnalytics.getSkuListForAllTitles(dashType, filtersCacheKey, analysisCacheKey, stockAnalysisClass);
    EdelweissAnalytics.hasSelectAllTitlesOnAllPagesTriggered = true;
    showLoadingDiv(null, dashType);
    getSkuListPromise.then(function (skuList) {
        skuList = skuList;
        if (Array.isArray(skuList)) {
            EdelweissAnalytics.rows = [];
            EdelweissAnalytics.selected = 0;
            for (var i = 0; i < skuList.length; i++) {
                EdelweissAnalytics.rows.push({ item: skuList[i], selected: 1 });
                EdelweissAnalytics.selected += 1;
            }
            $(".itemCheck").addClass("checkmark_checked");
            $(".itemRowNumber").addClass("num_checked");
            $(".checkVisual").addClass("red-stripe");

            EdelweissAnalytics.setStateOfSelectAllTitlesOnCurrentPageButton();
            EdelweissAnalytics.setStateOfSelectAllTitlesOnAllPagesButton();
            EdelweissAnalytics.handleSelectedItems(EdelweissAnalytics.selected);
            window.getResults();
        }
        closeLoadingDiv(null, dashType);
        EdelweissAnalytics.hasSelectAllTitlesOnAllPagesTriggered = false;
    });
};

EdelweissAnalytics.isAggregateStore = function (storeId) {
    return storeId !== null && storeId.length > 0
        && storeId[storeId.length - 1].toUpperCase() === "A";
};

EdelweissAnalytics.changeListViewOption = function (listTypeId, dashType, laneKey, prefReferenceCode) {
    var listType = parseInt(listTypeId);
    EdelweissAnalytics.listType[dashType] = listType;
    if (listType === EdelweissAnalytics.listTypes.Analytics_ISBNView) {
        EdelweissAnalytics.filterOptions[dashType].referenceCode = EdelweissAnalytics.referenceCodes.IsbnView;
    }
    else {
        EdelweissAnalytics.filterOptions[dashType].referenceCode = EdelweissAnalytics.referenceCodes.FamilyView;
    }

    async.series([
        async.apply(EdelweissAnalytics.saveAnalyticsUserPreference,
            prefReferenceCode, EdelweissAnalytics.filterOptions[dashType].referenceCode)
    ], function (err, results) {
        if (err) {
            console.error("Failed to save reference code user preference.");
        } else {
            if ($("#analytics_content").is(":visible") && $("#analytics_content").length > 0) {
                EdelweissAnalytics.reLoadAnalyticsDetail[dashType] = true;
            }
            EdelweissAnalytics.isFirstTimeLaneLoad[dashType] = true;
            EdelweissAnalytics.startLaneUpdateProcess(laneKey);
        }
    });
};

EdelweissAnalytics.addToIngramList = function () {
    var skuList = window.getSelectedItems();

    $('#popover_actions').slideUp();
    $('#popover_listView_block').hide();
    $('#popover_analyticsListView_block').hide();

    url = "/GetTreelineControl.aspx?controlName=/uc/ingram/AddToIpageList.ascx&skuList=" + skuList;
    openModal(url, "670px", "430px");
};

EdelweissAnalytics.addToActionPlan = function (isWeedingAnalysis) {
    if (isWeedingAnalysis) {
        var skuAndItemList = window.getSelectedItems();
        var itemIds = [];
        var skus = [];
        for (var i = 0; i < skuAndItemList.length; i++) {
            var skuAndItem = skuAndItemList[i].split(",");
            if (skuAndItem.length > 1) {
                var sku = skuAndItem[0];
                var itemId = skuAndItem[1];
                itemIds.push(itemId);
                skus.push(sku);
            }
        }
        var itemCount = itemIds.length;
        if (itemCount === 0) {
            modalAlert(getRes("must_select_one_or_more"));
        } else {
            $('#popover_actions').slideUp();
            $('#popover_listView_block').hide();
            $('#popover_analyticsListView_block').hide();
            AddItemsToPlan(skus, itemIds);
        }
    } else {
        var skuList = window.getSelectedItems();
        var titleCount = skuList.length;
        if (titleCount === 0) {
            modalAlert(getRes("must_select_one_or_more"));
        } else {
            $('#popover_actions').slideUp();
            $('#popover_listView_block').hide();
            $('#popover_analyticsListView_block').hide();
            AddItemsToPlan(skuList);
        }
    }


};

EdelweissAnalytics.generatePieSlicesDivs = function (index, className) {
    var dHtml = "<div style='position: relative; height: 30px; width: inherit; margin-left: 20%'>";
    dHtml += "<div class='table_PieContainer'><div class='table_pieBackground'></div>";
    dHtml += "<div id='pieSlice1_" + className + "_" + index + "' class='tableHold pieSlice1'><div class='tablePie'></div></div>";
    dHtml += "<div id='pieSlice2_" + className + "_" + index + "' class='tableHold pieSlice2'><div class='tablePie'></div></div>";
    dHtml += "<div class='table_marketLabelContainer'><div class='table_marketLabel' id='market_" + className + "_" + index + "'></div></div>";
    dHtml += "</div></div>";
    return dHtml;
};

EdelweissAnalytics.stylePercentageData = function (grid) {
    var marketColumnNames = ["col-ordered", "col-sold", "col-owned"];
    for (var i = 0; i < marketColumnNames.length; i++) {
        var columnName = marketColumnNames[i];
        (function (columnName) {
            grid.find("." + columnName + ":not(th)").each(function (index) {
                var value = $(this).text();
                var className = $(this).attr("class").split(" ")[1];
                $(this).empty();
                var dHtml = EdelweissAnalytics.generatePieSlicesDivs(index, className);
                $(this).append(dHtml);
                $(this).find("#market_" + className + "_" + index).html(value);
                EdelweissAnalytics.drawCircle(index, className, value, $(this));
            });
        })(columnName);
    }
};

EdelweissAnalytics.drawCircle = function (index, className, value, parentSelector) {
    value = value.replace("%", "") / 100;
    var totalAngle = 360 * value;
    var slice1 = 0;
    var slice2 = 0;
    if (totalAngle <= 180) {
        slice1 = totalAngle;
    } else {
        slice1 = 180;
        slice2 = totalAngle - 180;
    }
    // since family datatable and child datatable use the same selector for pieSlices, parentSelector is needed for finding the right element
    parentSelector.find("#pieSlice1_" + className + "_" + index + " .tablePie").css("-webkit-transform", "rotate(" + slice1 + "deg)");
    parentSelector.find("#pieSlice1_" + className + "_" + index + " .tablePie").css("-moz-transform", "rotate(" + slice1 + "deg)");
    parentSelector.find("#pieSlice1_" + className + "_" + index + " .tablepPie").css("-o-transform", "rotate(" + slice1 + "deg)");
    parentSelector.find("#pieSlice1_" + className + "_" + index + " .tablePie").css("-transform", "rotate(" + slice1 + "deg)");

    parentSelector.find("#pieSlice2_" + className + "_" + index + " .tablePie").css("-webkit-transform", "rotate(" + slice2 + "deg)");
    parentSelector.find("#pieSlice2_" + className + "_" + index + " .tablePie").css("-moz-transform", "rotate(" + slice2 + "deg)");
    parentSelector.find("#pieSlice2_" + className + "_" + index + " .tablePie").css("-o-transform", "rotate(" + slice2 + "deg)");
    parentSelector.find("#pieSlice2_" + className + "_" + index + " .tablePie").css("-transform", "rotate(" + slice2 + "deg)");
};

EdelweissAnalytics.showAllTitlesFromAllPieChartWedges = function (laneKey, dashType) {
    EdelweissAnalytics.saveShowAllPreference(laneKey, true, function () {
        EdelweissAnalytics.filterOptions[dashType].includeTitlesFromAllStockAnalysisClasses = true;
        EdelweissAnalytics.startLaneUpdateProcess(laneKey);
    });
}

EdelweissAnalytics.onAnalyticsDisplayModesSelectorChange = function (elem, dashType, laneKey, prefReferenceCode) {
    // "checked" means the ball is on the right side
    // The checked state here reflects what happened after the change
    var newListType = elem.attr("checked") === "checked" ?
        getEnumValue("listType", "ANALYTICS_FAMILYVIEW") : getEnumValue("listType", "ANALYTICS_ISBNVIEW");
    EdelweissAnalytics.changeListViewOption(newListType, dashType, laneKey, prefReferenceCode);
};

function titleGridPageLengthChange(tableSelector) {
    $(tableSelector).on('length.dt', function (e, settings, len) {
        // save the changes of the display length when it is the weeding lane. For other lanes, saving happens on the server side
        if (tableSelector == "#weedingGrid") {
            $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", { type: 'analytics', name: 'titleGridDisplayLength', value: len },
                function (data) {
                    if (data.code == "OK") {
                        settings.iDisplayLength = len;
                        EdelweissAnalytics.titleGridPageLength = len;
                    } else {
                        alert("There was an error saving detail grid length");
                    }
                });
        } else {
            EdelweissAnalytics.titleGridPageLength = len;
        }
    });
}

EdelweissAnalytics.getChartDivByLaneKeyAndChartIndex = function (laneKey, chartIndex) {
    return document.getElementById('chartFrame' + laneKey + '_' + chartIndex);
}

EdelweissAnalytics.renderBarcodeImg = function (data) {
    var isBarcodeNum = /^\d+$/.test(data.barcode);
    if (isBarcodeNum) {
        return '<img src="' + data.barcodeImgSrc + '"/>';
    } else return ''
}

EdelweissAnalytics.addCheckmarkToJacketCover = function (numOnHand, numOnOrder, sku, resultType, isFamilyMode) {
    var $checkmarkOnJacketCover = $("#checkmarkOnJacketCover_" + resultType + "_" + sku);
    if (numOnHand > 0) {
        var onHandMsg = isFamilyMode ? numOnHand + " " + getRes("versions_of_this_title_on_hand") :
            numOnHand + " " + getRes("on_hand");
        $checkmarkOnJacketCover.attr("title", onHandMsg);
        $checkmarkOnJacketCover.css("color", EdelweissAnalytics.colors.greenLight);
        $checkmarkOnJacketCover.show();
    } else if (numOnOrder > 0) {
        var onOrderMsg = isFamilyMode ? numOnOrder + " " + getRes("versions_of_this_title_on_order") :
            numOnOrder + " " + getRes("on_order");
        $checkmarkOnJacketCover.attr("title", onOrderMsg);
        $checkmarkOnJacketCover.css("color", EdelweissAnalytics.colors.yellow);
        $checkmarkOnJacketCover.show();
    }
};

EdelweissAnalytics.getSiteAreaFromLaneKey = function (laneKey) {
    return getEnumValue("edelweissAnalyticsSiteArea", laneKey.toUpperCase());
};

EdelweissAnalytics.getDashTypesOfVisibleLanes = function () {
    return _.keys(_.pickBy(window.EdelweissAnalytics.isVisibleLane, function (isVisible) { return isVisible; }));
};;
// JQuery Tag Plug-In
// Version 1.0
// Jason Belaire - Above The Treeline

(function ($) {
	$.fn.tag = function(options) {
		var defaults = {
			id: "",
			allowEdit: true,
			tagClassRoot: "tag",
			tagList: "",
			tagArray: [],
			tagReadOnly1List: "",
			tagReadOnly1Array: "",
			tagReadOnly1AllowDups: false,
			tagReadOnly2List: "",
			tagReadOnly2Array: "",
			tagReadOnly2AllowDups: false,
			tagAutoCompleteList: "",
			tagAutoCompleteArray: "",
			tagAutoCompleteHiddenInput: "",
			tagAutoCompleteListDelimiter: "~",
			tagListDelimiter: ",",
			onPreAddEventHandler: null,
			onPostAddEventHandler: null,
			onPreRemoveEventHandler: null,
			onPostRemoveEventHandler: null,
			onInitializedHandler: null,
			autocompleteResultsClass: "ac_results",
			ac_width: 0,
            focusOn: 0
		};
		
		var options = $.extend(defaults, options);
		
		if(options.id == "")
			options.id = $(this).attr("id");

		var elem = $(this);
		var markup = "<li class=\"" + options.tagClassRoot + "-new\">";
		markup += "<input id=\"" + options.id + "input\" class=\"" + options.tagClassRoot + "-input\" type=\"text\" size=\"10\"/></li>";
		markup += "<input name=\"" + options.id + "\" type=\"hidden\" style=\"display:none;\" value=\"\">";
		
		elem.addClass(options.tagClassRoot);
		elem.html(markup);
		
		var input = $(document.getElementById(options.id + "input"));
		
		if (options.focusOn == 0) {
		    input.hide();
		} else {
		    input.show().focus();
		}
		
		if(options.tagList != "")
			options.tagArray = options.tagList.split(options.tagListDelimiter);
		
		if(options.tagReadOnly1List != "")
			options.tagReadOnly1Array = options.tagReadOnly1List.split(options.tagListDelimiter);
		
		if(options.tagReadOnly2List != "")
			options.tagReadOnly2Array = options.tagReadOnly2List.split(options.tagListDelimiter);
		
		if(options.tagAutoCompleteList != "")
		    options.tagAutoCompleteArray = options.tagAutoCompleteList.split(options.tagAutoCompleteListDelimiter);
			
        if(options.tagAutoCompleteHiddenInput != "") {
            var value = $("#" + options.tagAutoCompleteHiddenInput).val();
            options.tagAutoCompleteArray = value.split(options.tagAutoCompleteListDelimiter);
        }
		
		var i;

		if(options.tagReadOnly1Array.length > 0)
			for(i = 0; i < options.tagReadOnly1Array.length; i++) {
                var tagValue = options.tagReadOnly1Array[i];

				if(isTagValueValid(tagValue))
				    addTag(tagValue, "readonly1");
			}
		
		if(options.tagReadOnly2Array.length > 0)
			for(i = 0; i < options.tagReadOnly2Array.length; i++) {
                var tagValue = options.tagReadOnly2Array[i];

				if(isTagValueValid(tagValue))
				    addTag(tagValue, "readonly2");
			}	
		
		if(options.tagArray.length > 0)
			for(i = 0; i < options.tagArray.length; i++) {
                var tagValue = options.tagArray[i];

				if(isTagValueValid(tagValue))
				    addTag(tagValue);
			}
        
		elem
            .click(function (e) {
                e.stopPropagation();
                e.preventDefault();
                if (options.allowEdit) {
                    if (e.target.tagName == "DIV") {
                        var index = $(e.target).parent().parent().index();
                        var value = getTagValue(index);
                        var tagIdx = options.tagArray.indexOf(value);
                        if (tagIdx > -1) {
                            options.tagArray.splice(tagIdx, 1);
                            triggerOnPreRemoveEvent(value, function (success) {
                                if (success) {
                                    removeTag(index);
                                    triggerOnPostRemoveEvent(value);
                                } else {
                                    options.tagArray.push(value);
                                }
                            });
                        }
                    }
			        
                    input.show().focus();
                }
            });

		input
		    .keydown(function (event) {
			if($("li.ac_over").length == 0 && options.allowEdit) {
			    if(event.which == 8 && getNewTagValue() == "") {
                    event.preventDefault();
                    event.stopPropagation();
			        if(isTagReadOnly(-1))
			            return;
    			    
                    if(elem
				        .children("input[name=\"" + options.id + "\"]")
				        .val().length == 0)
				        return;
		    
                    var tagValue = getTagValue(-1);
                    var idx = options.tagArray.indexOf(tagValue);
                    if (idx < 0) {
                        return;
                    }
                    options.tagArray.splice(idx, 1);

                    triggerOnPreRemoveEvent(tagValue, function (success) {
                        if (success) {
                            removeTag(idx);
                            triggerOnPostRemoveEvent(tagValue);
                        } else {
                            options.tagArray.push(tagValue);
                        }
                    });
			    } else if (event.which == 9 || event.which == 13) {
                    handleAddEvent(event);
			    }
			}
		});
		
		input
		    .keypress(function (event) {
			if(options.allowEdit) {
			    if(event.which == 59 || event.which == 44) {
                    handleAddEvent(event);
			    }
			}
		});

        input
            .autocomplete(options.tagAutoCompleteArray, {
                selectFirst: false,
                resultsClass: options.autocompleteResultsClass,
                width: options.ac_width,
                max: 20
            })
            .result(function(event, item) {
            if(item) {
                var tagValue;
                
                tagValue = item[0];
                    
                setNewTagValue(tagValue);
                
                if (isTagValueValid(tagValue))
                    triggerOnPreAddEventAndHandleResult(tagValue);
                        
                setNewTagValue("");
            }
		});
		triggerOnInitializedEvent(value);

        function handleAddEvent(event) {
            event.preventDefault();
            event.stopPropagation();

            var tagValue = getNewTagValue();

            if (isTagValueValid(tagValue)) {
                triggerOnPreAddEventAndHandleResult(tagValue);
            } else {
                resetInput();
            }
        }

        function triggerOnPreAddEventAndHandleResult(tagValue) {
            if (options.tagArray.indexOf(tagValue) > -1) {
                resetInput();
                return;
            }
            options.tagArray.push(tagValue);
            triggerOnPreAddEvent(tagValue, function (success) {
                if (success) {
                    options.tagArray.push(tagValue);
                    addTag(tagValue);
                    triggerOnPostAddEvent(tagValue);
                } else {
                    var idx = options.tagArray.indexOf(tagValue);
                    options.tagArray.splice(idx, 1);
                }
                resetInput();
            });
        }

        function resetInput() {
            setNewTagValue("");
            $("div.ac_results").hide();
        }

		function triggerOnInitializedEvent(value) {
		    var success = true;
		    
		    if(options.onInitializedHandler != null)
		        success = options.onInitializedHandler(value);

		    return success;
		}
		
		function triggerOnPreAddEvent(value, callback) {
            var success = true;

		    if(options.onPreAddEventHandler != null)
		        success = options.onPreAddEventHandler(value, callback);

		    return success;
		}
		
		function triggerOnPostAddEvent(value) {
		    var success = true;
		    
		    if(options.onPostAddEventHandler != null)
		        success = options.onPostAddEventHandler(value);
		    
		    return success;
		}
		
		function triggerOnPreRemoveEvent(value, callback) {
		    var success = true;
		    
		    if(options.onPreRemoveEventHandler != null)
		        success = options.onPreRemoveEventHandler(value, callback);
		    
		    return success;
		}
		
		function triggerOnPostRemoveEvent(value) {
		    var success = true;
		    
		    if(options.onPostRemoveEventHandler != null)
		        success = options.onPostRemoveEventHandler(value);
		    
		    return success;
		}
		
        function addTag(value, tagType, element) {
		    if(element)
                elem = $(element);

			if(tagType == undefined)
                tagType = "tag";

            var htmlAttributeEncodedValue = htmlAttributeEncode(value);
            var htmlEncodedValue = htmlEncode(value);

			var tag = "<li class=\"" +  options.tagClassRoot + "-" + tagType + "\">";

			tag += "<div class=\"tagValue\" style=\"float:left;\"><div class=\"tagDisplayName\" style=\"float:left;\"><span>" + htmlEncodedValue + "</span></div>";
			
			if(tagType == "tag")
                tag += "<div class=\"column close icon-close-icon iconSVG\" data-tag='" + htmlAttributeEncodedValue + "'></div>";
				
	        tag += "</div>";
				
	        tag += "<input class=\"selNewTag\" type=\"hidden\" style=\"display:none;\"";
			tag += "value=\"" + htmlAttributeEncodedValue + "\">";
			tag += "</li>\n";
			
			var tagValues = elem
				.children("input[name=\"" + options.id + "\"]")
				.val();
				
            if (tagValues == "") {
                tagValues = value;
            } else {
	            tagValues += options.tagListDelimiter + value;
            }
						
			elem
				.children("input[name=\"" + options.id + "\"]")
				.val(tagValues);
			
			var insertPos = elem
				.children("." + options.tagClassRoot + "-new")
				.children("." + options.tagClassRoot + "-input")
				.parent();
			
			$(tag).insertBefore(insertPos);
            
			setNewTagValue("");
		}
		
		function removeTag(index, element) {
		    if(element)
                elem = $(element);

			var tags = elem.children("." + options.tagClassRoot + "-readonly1, ." + options.tagClassRoot + "-readonly2, ." + options.tagClassRoot + "-tag");
			
			if(index === undefined || index == -1 || index > $(tags).size() - 1)
				index = $(tags).size() - 1;
			
			var tagValues = "";
			
			$(tags).each(function(i) {
				if(i == index && $(this).hasClass(options.tagClassRoot + "-tag"))
					$(this).remove();
				else {
					var tagValue = $(this).children("input").val();
					
					if(tagValues == "")
						tagValues += tagValue;
					else
						tagValues += options.tagListDelimiter + tagValue;
				}
			});
			
			elem
				.children("input[name=\"" + options.id + "\"]")
				.val(tagValues);
		}		
		
		function getTagValue(index, element) {
		    if(element)
		        elem = $(element);
		        
		    var tags = elem.children("." + options.tagClassRoot + "-readonly1, ." + options.tagClassRoot + "-readonly2, ." + options.tagClassRoot + "-tag");
		    
		    if(index === undefined || index == -1 || index > $(tags).size() - 1)
                index = $(tags).size() - 1;

            var value = unescape($(tags).eq(index).children("input").val());
			
			return value;
		}
		
		function isTagValueValid(value, element) {
		    if(value == "")
			    return false;
			    
            if(element)
			    elem = $(element);
			
			var isTagValueValid = true;
			if(!options.tagReadOnly1AllowDups)
			    elem
				    .children("." +  options.tagClassRoot + "-new")
				    .children("." +  options.tagClassRoot + "-input")
				    .parents("ul")
				    .children("." +  options.tagClassRoot + "-readonly1")
				    .each(function(i) {
				    var tagValue = unescape($(this).children("input").val());
    				
				    if(value == tagValue)
					    isTagValueValid = false;
			    });
			    
			if(!options.tagReadOnly2AllowDups)
			    elem
				    .children("." +  options.tagClassRoot + "-new")
				    .children("." +  options.tagClassRoot + "-input")
				    .parents("ul")
				    .children("." +  options.tagClassRoot + "-readonly2")
				    .each(function(i) {
				    var tagValue = unescape($(this).children("input").val());
    				
				    if(value == tagValue)
					    isTagValueValid = false;
			    });
			    
			elem
				.children("." +  options.tagClassRoot + "-new")
				.children("." +  options.tagClassRoot + "-input")
				.parents("ul")
				.children("." +  options.tagClassRoot + "-tag")
				.each(function(i) {
				var tagValue = unescape($(this).children("input").val());
				
				if(value == tagValue)
					isTagValueValid = false;
			});
			
			return isTagValueValid;
		}
				
		function isTagReadOnly(index, element) {
		    if(element)
		        elem = $(element);
		        
		    var tags = elem.children("." + options.tagClassRoot + "-readonly1, ." + options.tagClassRoot + "-readonly2, ." + options.tagClassRoot + "-tag");
		    
		    if(index === undefined || index == -1 || index > $(tags).size() - 1)
				index = $(tags).size() - 1;
				
			return $(tags).eq(index).hasClass(options.tagClassRoot + "-readonly1") || $(tags).eq(index).hasClass(options.tagClassRoot + "-readonly2");
		}
		
		function getNewTagValue(element) {
		    if(element)
		        elem = $(element);
		        
			var tagValue = elem
				.children("." + options.tagClassRoot + "-new")
				.children("." + options.tagClassRoot + "-input")
				.val();
				
			tagValue = tagValue.replace(/,+$/,"");
			tagValue = tagValue.replace(/^\s+|\s+$/g,"");
			
			return tagValue;
		}
		
		function setNewTagValue(tagValue, element) {
		    if(element)
		        elem = $(element);
		
			tagValue = tagValue.replace(/,+$/,"");
			tagValue = tagValue.replace(/^\s+|\s+$/g,"");
			
			elem
				.children("." + options.tagClassRoot + "-new")
				.children("." + options.tagClassRoot + "-input")
				.val(tagValue);
		}
		
		$.fn.extend({
            addTag: function(value, tagType) {
                addTag(value, tagType, this);
            },
            removeTag: function(index) {
                removeTag(index, this);
            },
            isTagValueValid: function(value) {
                return isTagValueValid(value, this);
            },
            isTagReadOnly: function(index) {
                return isTagReadOnly(index, this);
            }
        });
	};
})(jQuery);
//# sourceURL=jquery.tag.js;
// JScript File
function CreateGroup( name, description) {
    if (name.length == 0){
        alert("Please provide a name for the group");
        return;
    }
	$.getJSON("/getJSONData.aspx?builder=CreateGroup", { name:name, description:description }, 
	function(data){
		$('body').trigger("groupsUpdated");
		alert (data.text);
	});
}
function SaveOrganizationPreference(type, name, value, callbackFunction){
    $.getJSON("/getJSONData.aspx?builder=SaveOrganizationPreference",{type:type,name:name,value:value},
        function(data){
           if (typeof callbackFunction === "function") {
               callbackFunction(data);
           }
     });
}
function SaveUserPreference(type, name, value, onSuccess, onFailure){
    $.getJSON("/getJSONData.aspx?builder=SaveUserPreference",{type:type,name:name,value:value},
        function(data){
           if (data.code == "OK"){
                if (typeof onSuccess == "function"){
                    onSuccess(data)
                }
           }
           else{
                if (typeof onFailure == "function"){
                    onFailure(data)
                }
           }
     });

}

function SaveUserPreferenceAsync(type, name, value) {
    var savingPromise = new Promise(function (resolve, reject) {
        $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", {type:type,name:name,value:value},
        function(data) {
            data.code == "OK" ? resolve(data) : reject(data);
        });
    });
    return savingPromise;
}

function changePassword(success) {
    var currentPassword = $('#current-password').val();
    var password = $('input#password').val();
    var verifyPassword = $('input#verifyPassword').val();
    var token = $('input#token').val();

    $.post('/GetJSONData.aspx?builder=ChangePasswordV2', {
        currentPassword: currentPassword,
        password: password,
        verifyPassword: verifyPassword,
        token: token
    }, function (data) {
        if (data.code === 'SUCCESS') {
            if (_.isFunction(success)) {
                success(data);
            }
        }
        alert(data.text);
    }, 'json');
}
 function editUser(userid)
    {
    changeBody('/user-admin/UserProfile.aspx?userid=' + userid + '&mode=edit');
    }
function createUser()
{
            changeBody('/user-admin/UserProfile.aspx?userid=&mode=create');    
}
function removeUser(userID)
{
	if (confirm('Are you sure you want to delete this user!'))
	{
	$.post("/getJSONData.aspx?builder=RemoveUser", {userID:userID}, 
		function(data){                              
			if (data.code == "SUCCESS"){
				changeBody('/all-admin/UserManagement.aspx');
			}
			else
			{
				alert(data.text);
			}
		}, "json");
	}
}
function deleteorg(OrgID, source) {
    if (confirm('Are you sure you want to delete this org? Only organizations with no users can be deleted.')) {
        $.post("/getJSONData.aspx?m=TreelineAdmin&builder=DeleteOrg", { OrgID: OrgID },
    function (data) {
        if (data.code == "SUCCESS") {
            if (source == 0) {
                alert(data.text);
                window.location.reload(true);
            }
            else {
                $("#del_" + OrgID).val("Deleted")
                $("#det_" + OrgID).val("Deleted")
                $("#del_" + OrgID).attr("disabled", "disabled")
                $("#det_" + OrgID).attr("disabled", "disabled")
            }
        }
        else {
            alert(data.text);
        }
    }, "json");
    }
}
function addremovegridaccess(OrgID, emailadd, CampaignID, action) {
    if (confirm('Are you sure you want to ' + action + ' access?')) {
        $.post("/getJSONData.aspx?m=TreelineAdmin&builder=AddorRemoveGridAccess", { OrgID: OrgID, emailadd: emailadd, CampaignID: CampaignID, action: action },
    function (data) {
        if (data.code == "SUCCESS") {
                if (action == "add") {
                    $("#" + CampaignID).html('Yes <a href="javascript:addremovegridaccess(\'' + OrgID + '\',\'' + emailadd + '\',' + CampaignID + ',\'remove\');"><img src="/images/common/cross-small.png" alt="Remove access" title="Remove access"/></a>')
                }
                else {
                    $("#" + CampaignID).html('No <a href="javascript:addremovegridaccess(\'' + OrgID + '\',\'' + emailadd + '\',' + CampaignID + ',\'add\');"><img src="/images/common/plus-small.png" alt="Remove access" title="Add access"/></a>')
                }
        }
        else {
            alert(data.text);
        }
    }, "json");
    }
}
function addremovetreelineaccess(StoreID, OrgID, UserID, Area, action) {
    $.post("/getJSONData.aspx?m=TreelineAdmin&builder=AddorRemoveTreelineAccess", { StoreID: StoreID, OrgID: OrgID, UserID: UserID, Area: Area, action: action },
    function (data) {
        if (data.code == "SUCCESS") {
            if (action == "add") {
                var sessionstring = $('#' + StoreID + 'session').val()
                $("#" + StoreID + "login").html('<a href="http://view.abovethetreeline.com/CA_Canvas.aspx?sess=' + sessionstring + '&page=DrillDown" target="_blank">Log In</a>&nbsp;<a href="javascript:addremovetreelineaccess(\'' + StoreID + '\',\'' + OrgID + '\',\'' + UserID + '\',\'login\',\'remove\');"><img src="/images/common/cross-small.png" alt="Remove access" title="Remove access"/></a>')
                if (Area != "login") {
                    $("#" + StoreID + Area).html('Yes <a href="javascript:addremovetreelineaccess(\'' + StoreID + '\',\'' + OrgID + '\',\'' + UserID + '\',\'' + Area + '\',\'remove\');"><img src="/images/common/cross-small.png" alt="Remove access" title="Remove access"/></a>')
                }
            }
            else {
                if (Area == "login") {
                    $("#" + StoreID + Area).html('No <a href="javascript:addremovetreelineaccess(\'' + StoreID + '\',\'' + OrgID + '\',\'' + UserID + '\',\'' + Area + '\',\'add\');"><img src="/images/common/plus-small.png" alt="Remove access" title="Add access"/></a>')
                    $("#" + StoreID + "AdminAccess").html('No <a href="javascript:addremovetreelineaccess(\'' + StoreID + '\',\'' + OrgID + '\',\'' + UserID + '\',\'AdminAccess\',\'add\');"><img src="/images/common/plus-small.png" alt="Remove access" title="Add access"/></a>')
                    $("#" + StoreID + "CatAccess").html('No <a href="javascript:addremovetreelineaccess(\'' + StoreID + '\',\'' + OrgID + '\',\'' + UserID + '\',\'CatAccess\',\'add\');"><img src="/images/common/plus-small.png" alt="Remove access" title="Add access"/></a>')
                    $("#" + StoreID + "BudgetAccess").html('No <a href="javascript:addremovetreelineaccess(\'' + StoreID + '\',\'' + OrgID + '\',\'' + UserID + '\',\'BudgetAccess\',\'add\');"><img src="/images/common/plus-small.png" alt="Remove access" title="Add access"/></a>')
                }
                else {
                    $("#" + StoreID + Area).html('No <a href="javascript:addremovetreelineaccess(\'' + StoreID + '\',\'' + OrgID + '\',\'' + UserID + '\',\'' + Area + '\',\'add\');"><img src="/images/common/plus-small.png" alt="Remove access" title="Add access"/></a>')
                }
            }
        }
        else {
            alert(data.text);
        }
    }, "json");
}
function openuserdialog(orgId, userId) {
    $.url = "/admin/user/UserDetails.aspx?OrgID=" + orgId + "&UserID=" + userId + "&dialogMode=true";
    $.sd.open({
        url: $.url,
        height: 520,
        width: 900,
        title: 'User Details',
        buttonList: {
            'Cancel': function () {
                $.sd.close();
            }
        }
    }); 
}
function saveOrgToRecentlyViewedList(orgid) {
    jQuery.post("/GetJSONData.aspx?m=Alpha&builder=SaveRecentlyViewedOrgs_Support", { orgid: orgid },
    function (data) {
        if (data.code == "SUCCESS") {
            window.ePlus.modules.dashboard.refreshWidgetsWithResultType(getEnumValue("resultType", "SUPPORTACCOUNTS"));
        }
        else {
            alert("An error occurred while saving this account to your recently viewed list");
        }
    }, "json");
}

function GenerateTreelineLoginEmail(OrgID, UserID) {
    $.post("/getJSONData.aspx?m=TreelineAdmin&builder=GenerateTreelineLoginEmail", { OrgID: OrgID, UserID: UserID },
    function (data) {
        if (data.code == "SUCCESS") {
            alert(data.text);
        }
        else {
            alert(data.text);
        }
    }, "json");
}
function ShowCreateOrgDialog(OrgID) {
    $.url = "/admin/organization/CreateNewOrganization.aspx?button=false&OrgID=" + OrgID;
    $.sd.open({
        url: $.url,
        height: 380,
        width: 780,
        title: 'Create New Organization',
        buttonList: {
            'Create': function () {
                SaveOrg();
                $.sd.close();
            },
            'Cancel': function () {
                $.sd.close();
            }
        }
    });
}
function transferUser(OrgID, UserID) {
    $.sd.open({
        url: "/admin/registration/IndividualProcessing.aspx?RequestID=0&OrgID=" + OrgID + "&UserID=" + UserID + "&proctype=search",
        height: null,
        width: 600,
        title: 'Transfer User to a Different Org',
        buttonList: {
            'Cancel': function () {
                $.sd.close();
            }
        }
    });
}
;
// JScript File
function getRes(key) {
    return (typeof window.res === "object" && window.res && window.res[key]) ? window.res[key] : key;
}

//The functions to open and close a modal pop-up
function openModal(url, width, height, onLoad, onClose, data, modalNavigation, closeButtonClass, modalInnerClass) {
    // Trigger a page interaction event
    $(window).trigger('interaction', [function () {
        WebuiPopovers.hideAll();

        if ($("#popModal").length > 0) {
            openChildModal(url, width, height, onLoad, onClose, data);
        } else {
            closeModal();
            createModal(url, width, height, onLoad, onClose, data, modalNavigation, closeButtonClass, modalInnerClass);
        }
    }]);
}

function openChildModal(url, width, height, onLoad, onClose, data) {
    if ($("#popModal").length > 0) {
        closeChildModalInitial();
        createChildModal(url, width, height, onLoad, onClose, data);
    } else {
        openModal(url, width, height, onLoad, onClose, data);
    }
}

function createModal(url, width, height, onLoad, onClose, data, modalNavigation, closeButtonClass, modalInnerClass) {
    createModalDialog(width, height, null, onClose, modalNavigation, closeButtonClass, modalInnerClass);
    var uniqueIdSuffix = "TitleModal";
    savingModal(getRes("loading"), uniqueIdSuffix);
    $("#popModal_inner").load(url, data, function () {
        closeSavingModal(uniqueIdSuffix);
        if (_.isFunction(onLoad)) {
            var elem = document.getElementById('popModal_content')
            onLoad(elem);
        }
    });
}

function createChildModal(url, width, height, onLoad, onClose, data, modalNavigation) {
    var dHtml = '<div id="popChildModal" class="modalChildFrame"></div>';
    dHtml += '<div id="popChildModal_content" class="modalChildContent" style="width:' + width + ';height:' + height + ';">';
    dHtml += '<div id="popChildModal_inner" style="width:100%;height:100%;position:relative;"></div>';
    dHtml += '<div id="popChildClose" title="' + getRes("close") + '" style="position: absolute; top: 10px; right: 10px;" class="icon-close-icon icon-btn-responsive iconSVG_Darker"></div>';
    dHtml += '</div>';
    $("#popModal_inner").append(dHtml);

    savingChildModal(getRes("loading"))
    $("#popChildModal_inner").load(url, data, function () {
        closeChildSavingModal()
        if (typeof onLoad === 'function') {
            var elem = document.getElementById('popChildModal_content');
            onLoad(elem);
        }
    });

    $("#popChildClose").on("click", function () {
        childModalClose(onClose);
    });

    $("#popChildModal").on("click", function () {
        childModalClose(onClose);
    });

    addToModalStack("#popChildModal", function () {
        childModalClose(onClose);
    });

    attachEscapeKeyEventListener();

    if (!_.isNil(modalNavigation)) {
        addModalNavigation(modalNavigation, $("#popChildModal"), $("#popChildModal_content"));
    }
}

function buildModalContent(css) {
    return $('<div />', {
        id: 'popModal_content',
        class: 'modalContent',
        css: css
    });
}

function buildPopModal() {
    return $('<div />', {
        id: 'popModal',
        class: 'modalFrame'
    });
}

function buildPopModalInner(innerClass) {
    return $('<div />', {
        id: 'popModal_inner',
        class: 'defaultModalInnerStyle  ' + innerClass
    });
}

function buildModalCloseButton(title, closeButtonClass) {
    return $('<div />', {
        id: 'mClose',
        class: closeButtonClass + ' icon-close-icon iconSVG_Darker icon-btn-responsive',
        title: title
    });
}

function createModalDialog(width, height, html, onClose, modalNavigation, closeButtonClass, modalInnerClass, zIndex) {
    if (typeof closeButtonClass === "undefined" || closeButtonClass === null) {
        closeButtonClass = "defaultModalCloseButton";
    }
    if (_.isNil(modalInnerClass)) {
        modalInnerClass = "";
    }
    var css = {
        width: width,
        height: height
    };
    if (zIndex) {
        css['z-index'] = zIndex;
    }

    var $modalPop = buildPopModal();
    var $modalContent = buildModalContent(css);
    $modalContent.append(buildPopModalInner(modalInnerClass));
    $modalContent.append(buildModalCloseButton(getRes("close"), closeButtonClass));
    var $form = $('#form1');
    $form.append($modalPop);
    $form.append($modalContent);
    if (html) {
        $("#popModal_inner").append(html);
    }
    $('html, body').css("overflow", "hidden");

    $("#mClose").on("click", function () {
        modalClose(onClose);
    });
    $("#popModal").on("click", function () {
        modalClose(onClose);
    });
    addToModalStack("#popModal", function () {
        modalClose(onClose);
    });
    attachEscapeKeyEventListener();
    if (!_.isNil(modalNavigation)) {
        addModalNavigation(modalNavigation, $("#popModal"), $("#popModal_content"));
    }
}

function modalClose(onClose) {
    var doClose = true;

    if ($.isFunction(onClose)) {
        doClose = onClose();
    }

    if (doClose) {
        $('#mClose').webuiPopover('hide');
        $('html, body').css("overflow", "auto");

        closeModal();
    }
}

function childModalClose(onClose) {
    $('#popChildClose').webuiPopover('hide');
    var doClose = true;

    if ($.isFunction(onClose)) {
        doClose = onClose();
    }

    if (doClose) {
        closeChildModal();
    }
}

function addModalNavigation(modalNavigation, $popModal, $popModalContent) {
    if ($popModal.length && $popModalContent.length) {
        $popModal.append("<a id='popModal_prevNavigation' class='popModal_Navigation clickable icon-chevron-left'></a>");
        $popModal.prepend("<a id='popModal_nextNavigation' class='popModal_Navigation clickable icon-chevron-right'></a>");

        positonModalNavigationArrows(modalNavigation, $popModalContent);
        setModalNavigationPosition(modalNavigation, $popModal, $popModalContent);
    }
}

function positonModalNavigationArrows(modalNavigation, $popModalContent) {
    var popModalContentOffsetLeft = $popModalContent.offset().left;
    var $prevNavigation = $("#popModal_prevNavigation");
    var $nextNavigation = $("#popModal_nextNavigation");
    var windowWidth = $(window).width();

    $prevNavigation.offset({ left: modalNavigation.prevNavigationOffset(popModalContentOffsetLeft, $prevNavigation) });
    $nextNavigation.offset({ left: modalNavigation.nextNavigationOffset(popModalContentOffsetLeft, windowWidth) });

    if (_.isFunction(modalNavigation.prevNavigation)) {
        $prevNavigation.off('click').on('click', modalNavigation.prevNavigation);
    }

    if (_.isFunction(modalNavigation.nextNavigation)) {
        $nextNavigation.off('click').on('click', modalNavigation.nextNavigation);
    }
}

function setModalNavigationPosition(modalNavigation, $popModal, $popModalContent) {
    if (modalNavigation.showPosition) {
        $popModal.append("<div id='navigationModalPosition' class='navigationModalPosition'><span id='modalNavigationCurrentPosition'>" + modalNavigation.currentPosition +
            "</span>/<span id='modalNavigationLastPosition'>" + modalNavigation.lastPosition + "</span></div>");

        $("#navigationModalPosition").offset({ top: modalNavigation.positionContentOffsetTop($popModalContent) });
    }
}

function createModalHTML(text, width, height, skipPadding, zindex, onClose) {
    WebuiPopovers.hideAll();
    $("#popModal").remove();
    var dHtml = '<div id="popModal" class="modalFrame"></div>';

    if (typeof zindex === 'undefined') {
        dHtml += '<div id="popModal_content" class="modalContent" style="width:' + width + ';height:' + height + ';background-color:#E9EBEC;padding:50px;">';
    } else {
        dHtml += '<div id="popModal_content" class="modalContent" style="width:' + width + ';height:' + height + ';background-color:#E9EBEC;padding:50px;z-index: ' + zindex + '">';
    }

    if (!skipPadding) {
        dHtml += "<div id='popModal_inner' style='padding:40px;background-color:#fff'>" + text + "</div>";
    } else {
        dHtml += "<div id='popModal_inner'>" + text + "</div>";
    }
    dHtml += '<div id="modal-close" style="position: absolute; top: 10px; right: 10px;" class="icon-close-icon iconSVG_Darker"></div>';
    dHtml += '</div>';
    $("#form1").append(dHtml);
    $("html, body").css("overflow", "hidden");

    $('#modal-close').on('click', function () {
        if (typeof onClose === 'function') {
            modalClose(onClose);
        } else {
            closeModal();
        }
    });
}

function closeModal(_validateFunction) {
    WebuiPopovers.hideAll();
    $('html, body').css("overflow", "auto");
    closeModalAction();

    if (!_.isNil(window.modalStack) && window.modalStack[window.modalStack.length - 1] === "#popModal") {
        window.modalStack.pop();
    }
}

function closeModalAction() {
    $(".help").webuiPopover("hide"); // Hide any active help popups
    if ($("#popChildModal").length > 0) {
        closeChildModal();
        closeSavingModal();
    } else {
        if ($("#popModal").length > 0) {
            $(".popModalButton").off("click");
            $("#popModal").remove();
            $("#popModal_content").remove();

            if (!$("#popModalFull").length > 0) {
                if (isIOs()) {
                    $("html, body").css({
                        "overflow": "scroll",
                        "-webkit-overflow-scrolling": "touch"
                    });
                } else {
                    $("html, body").css({
                        'overflow': "auto",
                        'height': "auto"
                    });
                }

                //Resizing was really slowing things down on the Dashboard. Removed it
                resizeThings("pop");
            }
        }
    }
}

function savingModal(iHtml, uniqueIdSuffix) {
    if (typeof uniqueIdSuffix === "undefined" || uniqueIdSuffix === null) {
        uniqueIdSuffix = "";
    }
    var dHtml = '<div id="savingModal' + uniqueIdSuffix + '" class="blockDivSaving saveBody">';
    dHtml += '<div class="saveContainer">';
    dHtml += '<div class="saveContent">';
    dHtml += '<div class="column" id="progressAnimation"></div>';
    dHtml += '<div class="columnSpaced">' + iHtml + '</div>';
    dHtml += '</div>';
    dHtml += '</div>';
    dHtml += '</div>';
    $("#popModal_inner").after(dHtml);
    $("#progressAnimation").html(templateCache.loadingAnimation({ svgLoaderClass: '' }));
}
/**
 * @param {string} iHtml - Custom HTML or text to render in overlay
 * @param {string} iControl - DOM element id to bind overlay to
 * @param {string} addedClass - Class to apply to the overlay
 * @param {string} customLoaderClass - Class to apply to the preloader
 */
function savingModalOverlay(iHtml, iControl, addedClass, customLoaderClass) {
    var addClass = "";
    if (!_.isNil(addedClass)) {
        addClass = addedClass;
    }
    var dHtml = '<div id="savingModal' + iControl + '" class="blockDiv saveBody ' + addClass + '">';
    dHtml += '<div class="saveContainer">';
    dHtml += '<div class="saveContent">';
    dHtml += '<div class="column" id="progressAnimation"></div>';
    dHtml += '<div class="columnSpaced">' + iHtml + '</div>';
    dHtml += '</div>';
    dHtml += '</div>';
    dHtml += '</div>';
    $("#" + iControl).append(dHtml);
    var height = $("#" + iControl).height();
    $("#savingModal" + iControl).css("height", height + "px");
    if (_.isNil(customLoaderClass)) {
        customLoaderClass = '';
    }
    $("#savingModal" + iControl + " #progressAnimation").html(templateCache.loadingAnimation({ svgLoaderClass: customLoaderClass }));
}
function closeSavingModalOverlay(iControl) {
    $("#savingModal" + iControl).remove();
}
function closeSavingModal(uniqueIdSuffix) {
    if (typeof uniqueIdSuffix === "undefined" || uniqueIdSuffix === null) {
        uniqueIdSuffix = "";
    }
    $("#savingModal" + uniqueIdSuffix).remove();
}

function doesSavingModalExist(domId) {
    return $("#savingModal" + domId).length > 0;
}

function addModalCloseEventHandler(onClose) {
    if ($.isFunction(onClose)) {
        $("#mClose").click(onClose);
    }
}

function savingChildModal(iHtml) {
    var dHtml = '<div id="savingChildModal" class="blockDivSaving saveBody">';
    dHtml += '<div class="saveContainer">';
    dHtml += '<div class="saveContent">';
    dHtml += '<div class="column" id="progressChildAnimation"></div>';
    dHtml += '<div class="columnSpaced">' + iHtml + '</div>';
    dHtml += '</div>';
    dHtml += '</div>';
    dHtml += '</div>';
    $("#popChildModal_inner").after(dHtml);
    $("#progressChildAnimation").html(templateCache.loadingAnimation({ svgLoaderClass: '' }));
}
function closeChildSavingModal() {
    $("#savingChildModal").remove();
}

function createChildModalHTML(bodyHtml, width, height) {
    var dHtml = '<div id="popChildModal" class="modalChildFrame" onclick="closeChildModal();"></div>';
    dHtml += '<div id="popChildModal_content" class="modalChildContent" style="width:' + width + ';height:' + height + ';">';
    dHtml += '<div id="popChildModal_inner" style="width:100%;height:100%;position:relative;"></div>';
    dHtml += '<div style="position: absolute; top: 10px; right: 10px;" class="icon-close-icon iconSVG_Darker" onclick="javascript:closeChildModal();"></div>';
    dHtml += '</div>';
    $("#popModal_inner").append(dHtml);
    $("#popChildModal_inner").html(bodyHtml);

    addToModalStack("#popChildModal", function () {
        childModalClose();
    });

    attachEscapeKeyEventListener();
}

function closeChildModal() {
    if ($("#popChildModal").length > 0) {
        $("#popChildModal").remove();
        $("#popChildModal_content").remove();
    } else {
        if ($("#popModal").length > 0) {
            closeModal();
        }
    }
}
function closeChildModalInitial() {
    $("#popChildModal").remove();
    $("#popChildModal_content").remove();
}

function openChildModalHTML(dHtml, width, height) {
    //closeChildModal();
    createChildModalHTML(dHtml, width, height);
    //$("#pop_content").load($.url);
}


//The functions to open and close a modal pop-up
function openModalFull(url, width, height, refresh) {
    closeModalFull(0);
    createModalFull(url, width, height, refresh);
}
function createModalFull(url, width, height, refresh) {
    var dHtml = '<div id="popModalFull" class="modalFrameFull" onclick="javascript:closeModalFull(' + refresh + ');"></div>';
    dHtml += '<div id="popModalFull_content" class="modalContentFull" style="width:' + width + ';height:' + height + ';">';
    dHtml += '<div id="popModalFull_inner" style="width:100%;height:100%;overflow-y:auto;overflow-x:hidden;"></div>';
    dHtml += '<div style="position: absolute; top: 10px; right: 10px;" class="icon-close-icon iconSVG fColor" onclick="javascript:closeModalFull();"></div>';
    dHtml += '</div>';
    $("#form1").append(dHtml);
    $("#popModalFull_inner").load(url);
    $('html, body').css("overflow", "hidden");
}
function closeModalFull(refresh) {
    if (refresh == 1) {
        window.changePage(location.hash);
    }
    if ($("#popModalFull").length > 0) {
        $("#popModalFull").remove();
        $("#popModalFull_content").remove();
        $("html, body").css({
            'overflow': "auto",
            'height': "auto"
        });
    }
}
function modalAlert(message) {
    createModalHTML(message, "500px", "100px");
}

function modalConfirm(config, doCreateChildModal) {
    if (!config) return;

    if (typeof config.buttons == "object") {
        if (!config.message) config.message = "";

        var i = 0;

        config.message += "<p style='text-align:center'>";

        var classString = "popModalButton";
        if (doCreateChildModal) {
            classString += " popChildModalButton";
        }

        $.each(config.buttons, function (k, v) {
            config.message += "<button id='popModalButton" + i +
                "' class='" + classString + "' type='button'>" + k +
                "</button>";
            i++;
        });

        config.message += "</p>";
        if (doCreateChildModal) {
            config.message = "<div class='child-modal-confirm-container'>" + config.message;
            config.message += "</div>";
        }
    }
    if (doCreateChildModal) {
        createChildModalHTML(config.message, config.width, config.height);
    } else {
        var onClose = config.onClose && typeof config.onClose === 'function' ? config.onClose : null;
        createModalHTML(config.message, config.width, config.height, false, config.zindex, onClose);
    }

    if (typeof config.buttons == "object") {
        var i = 0;

        $.each(config.buttons, function (k, v) {
            if (typeof v == "function") {
                $("#popModalButton" + i).on("click", v);
            }
            i++;
        });
    }
}

function modalPrompt(title, defaultValue, onSubmit, width, height, formId) {
    var title = title || "&nbsp;",
        formId = formId || "modalPromptForm",
        inputId = formId + "Value",
        saveIconId = formId + "SaveIcon",
        inputValue = defaultValue || "",
        width = width || "450px",
        height = height || "150px",
        html = "<form id='" + formId + "' style='width:100%'>" +
            "    <div style='margin:20px'>" +
            "        <div style='margin-bottom:20px'>" + title + "</div>" +
            "        <div>" +
            "            <div class='column'>" +
            "                <input id='" + inputId + "' name='" + inputId + "' value='" + htmlEncode(inputValue) + "' class='genericInput overallStyles' />" +
            "            </div>" +
            "            <div id='" + saveIconId + "' class='columnSpaced icon-save-icon iconSVG_Darker' style='margin-left:25px; margin-top:8px'></div>" +
            "        </div>" +
            "    </div>" +
            "</form>";

    createModalDialog(width, height, html);

    var form = $("#" + formId),
        input = $("#" + inputId),
        saveIcon = $("#" + saveIconId);

    // dynamically size input width based on form width
    input.parent().width(form.width() - 84);

    saveIcon.on("click", function () {
        form.submit();
    });

    form.on("submit", function (e) {
        if (typeof onSubmit == "function") {
            var value = input.val();
            onSubmit(value, e);
        }

        return false;
    });

    setTimeout(function () { input.select().focus() }, 0);
}

function flexibleBlock(zIndexVar) {
    var dHtml = '<div class="modalGeneric" style="z-index:' + zIndexVar + '" onclick="javascript:flexibleRemoveBlock();"></div>';
    $("#form1").append(dHtml);
    $("html, body").css("overflow", "hidden");
}

function flexibleRemoveBlock() {
    $(".modalGeneric").remove();
    $("html, body").css({
        'overflow': 'auto',
        'height': 'auto'
    });
}

function savingModalControl(iHtml, controlId) {
    var dHtml = '<div id="savingModal" class="blockDiv saveBody">';
    dHtml += '<div class="saveContainer">';
    dHtml += '<div class="saveContent">';
    dHtml += '<div class="column" id="progressAnimation"></div>';
    dHtml += '<div class="columnSpaced">' + iHtml + '</div>';
    dHtml += '</div>';
    dHtml += '</div>';
    dHtml += '</div>';
    $("#" + controlId).html(dHtml);
    $("#progressAnimation").html(templateCache.loadingAnimation({ svgLoaderClass: '' }));
}

function savingModalControlGeneric(iHtml, controlId) {
    var dHtml = '<div id="savingModal" class="blockDiv saveBody">';
    dHtml += '<div class="saveContainer">';
    dHtml += '<div class="saveContent">';
    dHtml += '<div class="column" id="progressAnimation_' + controlId + '"></div>';
    dHtml += '<div class="columnSpaced">' + iHtml + '</div>';
    dHtml += '</div>';
    dHtml += '</div>';
    dHtml += '</div>';
    $("#" + controlId).html(dHtml);
    $("#progressAnimation_" + controlId).html(templateCache.loadingAnimation({ svgLoaderClass: '' }));
}

function closeSavingModalControl(controlId) {
    $("#" + controlId).html("");
}

//Function to open Stylized Select Box
function dropDownFilter(option) {
    $(".typeOptionExpandable_Arrow").addClass("icon-drop-down-icon");
    $(".typeOptionExpandable_Arrow").removeClass("icon-drop-up-icon-01");
    if ($("#" + option).css("display") === "none") {
        $(".dropDownOptions").hide();
        $("#" + option).show();
        $("." + option + "_Arrow").addClass("icon-drop-up-icon-01");
        $("." + option + "_Arrow").removeClass("icon-drop-down-icon");
    } else {
        $(".dropDownOptions").hide();
        $("." + option + "_Arrow").addClass("icon-drop-down-icon");
        $("." + option + "_Arrow").removeClass("icon-drop-up-icon-01");
    }
}

///////Specific Calls

//Open Markup Note Editor
function openMarkupEditor(sku, mailingId, catalogId, trunc, e) {
    var url = '/EditMailingNote.aspx?sku=' + sku + '&mailingID=' + mailingId + '&catalogID=' + catalogId + '&truncate=' + trunc;
    var id = 'markupEditor_' + sku;
    var clickSelector = 'a, button';
    var clickEvent = 'click.' + id;
    var beforeUnloadEvent = 'beforeunload.' + id;

    openDialog({
        id: id,
        url: url,
        doAutoOpen: false,
        onBeforeShow: function ($dialog) {
            disableMarkupNoteEditor(sku);
            $('#autoComplete').prop('disabled', true);
            enableMarkupFooterBlock();  // Prevent interaction with the markup bar
            WebuiPopovers.disableAll(); // Prevent interaction with popovers

            var message = getRes('warning_markup_editor_close').replace('{0}', sku);

            // In addition to blocking the markup bar, we also want to capture any
            // DOM events that should trigger closing the markup editor dialog box.
            // If the user triggers any of the events below, we want to prompt
            // the user to let them know that continuing with the action will close
            // the markup editor dialog without saving changes.

            // 1.) Page interactions (i.e. opening dialog boxes or hashchange events)
            window.interactionHandler = function (e, callback, cancelCallback) {
                var callCancelCallback = function () {
                    if (typeof cancelCallback === 'function') {
                        cancelCallback(e);
                    }
                };

                if (confirm(message)) {
                    $dialog.trigger('close');

                    if (typeof callback === 'function') {
                        callback(e);
                    } else {
                        callCancelCallback();
                    }
                } else {
                    callCancelCallback();
                }
            }

            // 2.) Clicking links/buttons on the page
            $('#interiorPageContent').on(clickEvent, clickSelector, function (e) {
                var $self = $(this);

                if (e) {
                    e.preventDefault();
                    e.stopPropagation();
                }

                // Leverage the interaction handler above
                $(window).trigger('interaction', [function () {
                    $self[0].click();
                }]);
            });

            // 3.) Reload/navigate away from the page
            $(window).off(beforeUnloadEvent).on(beforeUnloadEvent, function (e) {
                e = e || window.event;
                e.returnValue = message;

                return message; // Most modern browsers will ignore this due to scammers, but it doesn't hurt
            });

            bindEditMailingNoteButtons(mailingId, sku);
        },
        onBeforeClose: function ($dialog) {
            // Before we close the markup editor dialog, we want to destroy the
            // CKEDITOR instance in the dialog to free up those resources.
            var editor = $dialog.data('editor');

            if (editor) {
                try {
                    editor.destroy(true);
                } catch (e) { }
            }
        },
        onClose: function () {
            $('#autoComplete').prop('disabled', false);
            disableMarkupFooterBlock();
            WebuiPopovers.enableAll();
            window.interactionHandler = null;
            $('#interiorPageContent').off(clickEvent, clickSelector);
            $(window).off(beforeUnloadEvent);
            enableMarkupNoteEditor(sku);
            setListViewProperty('activeMarkupNoteEditor', null);
        }
    });
}

function bindEditMailingNoteButtons(mailingId, sku) {
    var markupEditor = window.ePlus.modules.inlineMarkupNoteEditor;
    var callback = function () { markupEditor.setIsSaving(false); }

    markupEditor.setIsSaving(false);
    
    $('#edit-mailing-note-save').off().on('click', function () {
        if (markupEditor.isSaving()) return;

        markupEditor.setIsSaving(true);
        saveNoteDialog(callback);
    });

    $('#edit-mailing-note-delete').off().on('click', function () {
        if (markupEditor.isSaving()) return;

        markupEditor.setIsSaving(true);
        markupEditor.deleteMarkupNoteAndCloseEditor(mailingId, sku, callback);
    });
}

//Open Export for All Selected Titles
function openTitleExport_Selected(outputType, output) {
    var skuList = window.getSelectedItems();
    var titleCount = skuList.length;
    if (titleCount === 0) {
        modalAlert(getRes("must_select_one_or_more"));
    } else {
        var url = "/GetTreelineControl.aspx?controlName=/uc/controls/exportTitles.ascx&sku=All&titles=" + titleCount;

        if (_.isInteger(outputType) && _.isInteger(output)) {
            url += "&outputType=" + outputType + "&output=" + output;
        }

        openModal(url, "920px", "540px", null, function () {
            // If a PrintStatus.aspx timer is defined, clear it
            if (typeof refreshId === "number") {
                clearInterval(refreshId);
            }
            closeModal();
        });
    }
    $('#popover_actions').slideUp();
    $('#popover_listView_block').hide();
    $('#popover_analyticsListView_block').hide();
}

// create export file for Bertrams stores with all selected Titles
function createBertlineExport_Selected() {
    var skulist = window.getSelectedItems();
    var titleCount = skulist.length;
    if (titleCount === 0) {
        modalAlert(getRes("must_select_one_or_more"));
    } else {
        $('#popover_actions').slideUp();
        $('#popover_listView_block').hide();
        $('#popover_analyticsListView_block').hide();

        var analysisCacheKey = EdelweissAnalytics.analysisCacheKey;
        var reportname = "Bertline";
        var exportType = getEnumValue("exportType", (reportname).toUpperCase());
        var stockAnalysisClass = EdelweissAnalytics.stockAnalysisClass;
        var orderId = 0;
        createAndExportForm(exportType, reportname, skulist, orderId, analysisCacheKey, stockAnalysisClass);
    }
}

function createAndExportForm(exportType, reportname, skulist, orderId, analysisCacheKey, stockAnalysisClass, filtersCacheKey, catalogId, mailingId, inventoryOnly) {
    $.url = "/order/export/POSExport.aspx";
    var form = "<form id='exportForm' target='_blank' action='" + $.url + "' method='POST'>" +
        "<input type='hidden' name='exportType' value='" + exportType + "'>" +
        "<input type='hidden' name='reportname' value='" + reportname + "'>" +
        "<input type='hidden' name='skuList' value='" + skulist + "'>" +
        "<input type='hidden' name='orderID' value='" + orderId + "'>" +
        "<input type='hidden' name='analysisCacheKey' value='" + _.escape(analysisCacheKey) + "'>" +
        "<input type='hidden' name='stockAnalysisClass' value='" + stockAnalysisClass + "'>" +
        "<input type='hidden' name='filtersCacheKey' value='" + _.escape(filtersCacheKey) + "'>" +
        "<input type='hidden' name='doSort' value='false'>" +
        "<input type='hidden' name='inventory' value='" + inventoryOnly + "'>";

    if (!_.isNil(catalogId)) {
        form += "<input type='hidden' name='catalogID' value='" + catalogId + "'>";
    }

    if (!_.isNil(mailingId)) {
        form += "<input type='hidden' name='mailingID' value='" + mailingId + "'>";
    }

    form = form + "</form>";
    $("#frameWrapper").html("");
    $("#frameWrapper").append(form);
    $("#exportForm").submit();
}

//Open Export for One Title
function openTitleExport(sku, outputType, output) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/controls/exportTitles.ascx&sku=" + sku;

    if (_.isInteger(outputType) && _.isInteger(output)) {
        url += "&outputType=" + outputType + "&output=" + output;
    }

    openModal(url, "920px", "540px");
}

function openClientNotes(OrgID, clientID) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/support/clientNotes.ascx&OrgID=" + OrgID + "&clientID=" + clientID;
    openModal(url, "700px", "525px");
}

function openDBStyleEditor(fixedElementID, multiple, templateId, mailingId, sku, itemnumber) {
    var url
    if (multiple == "true") {
        url = "/GetTreelineControl.aspx?controlName=/templates/styleEditor/db_multipleEditor.ascx&multiple=true&templateID=" + templateId + "&FixedElementID=" + fixedElementID + "&selectedMailingID=" + mailingId + "&itemnumber=" + itemnumber + "&SKU=" + sku;
        openModal(url, "900px", "525px");
    } else {
        url = "/GetTreelineControl.aspx?controlName=/templates/styleEditor/db_styleEditor.ascx&multiple=" + multiple + "&templateID=" + templateId + "&fixedElementID=" + fixedElementID + "&selectedMailingID=" + mailingId + "&itemnumber=" + itemnumber + "&SKU=" + sku;
        openModal(url, "600px", "525px");
    }
}

function openAddElement(elementID, element, templateID) {
    var url = "/GetTreelineControl.aspx?controlName=/templates/styleEditor/AttributeSelect.ascx&fixedElementID=" + elementID + "&selAttr=" + element + "&templateID=" + templateID;
    openModal(url, "500px", "500px");
}

function AddEditAttribute(row) {
    var url = "/GetTreelineControl.aspx?controlName=/templates/styleEditor/StyleAttributeEditor.ascx&row=" + row;
    openModal(url, "400px", "200px");
}

function addNewDBField(templateID, sku, mailingID) {
    var url = "/GetTreelineControl.aspx?controlName=/templates/styleEditor/AddNewField.ascx&templateID=" + templateID + "&sku=" + sku + "&mailingID=" + mailingID;
    alert(url)
    openModal(url, "560px", "600px");
}

function EditAttributesFree() {
    var url = "/GetTreelineControl.aspx?controlName=/templates/styleEditor/StyleAttributeEditorFree.ascx";
    openModal(url, "400px", "400px");
}

function openClientContacts(OrgID, clientID) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/support/client_contacts.ascx&OrgID=" + OrgID + "&clientID=" + clientID;
    openModal(url, "600px", "525px");
}

function openContactWindow(clientID, orgID, contactID) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/support/client_contact_creator.ascx&OrgID=" + orgID + "&clientID=" + clientID + "&contactID=" + contactID;
    openMultiModal({
        id: 'support-contact',
        url: url,
        width: "500px",
        height: "425px"
    });
}

function openSupportUserNote(appUserId, ticketID, ticketLevel, pageSource) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/support/noteCreator.ascx&appUserID=" + appUserId + "&ticketID=" + ticketID + "&noteLevel=" + ticketLevel + "&pageSource=" + pageSource;
    openMultiModal({
        id: 'support-note',
        url: url,
        width: "480px",
        height: "420px"
    });
}

function openSupportAccountNote(orgId, ticketID, ticketLevel, pageSource) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/support/noteCreator.ascx&orgId=" + orgId + "&ticketID=" + ticketID + "&noteLevel=" + ticketLevel + "&pageSource=" + pageSource;
    openMultiModal({
        id: 'support-note',
        url: url,
        width: "480px",
        height: "420px"
    });
}

function openNewSupportNote(pageSource) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/support/noteCreator.ascx&allowAccountSelect=true&pageSource=" + pageSource;
    openModal(url, "560px", "400px");
}

function openCreateFreshDeskTicket() {
    var url = "/GetTreelineControl.aspx?controlName=/uc/freshdesk/CreateFreshDeskTicket.ascx";
    openMultiModal({
        id: 'freshdesk',
        url: url,
        width: "700px",
        height: "425px"
    });
}

//Open Community Reviews
function openCommunityTitleReviews(familyItem, sku, reviewType) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/controls/communityTitleReviews.ascx&familyItem=" + familyItem + "&sku=" + sku + "&reviewType=" + reviewType;
    openModal(url, "900px", "625px");
}

//Open Community Shelves
function openCommunityTitleShelves(familyItem, sku) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/controls/communityTitleShelves.ascx&familyItem=" + familyItem + "&sku=" + sku;
    openModal(url, "870px", "625px");
}

//Open Select Organization
function selectOrganization(orgId, selectedMarkupId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/contacts/selectOrganization.ascx&targetOrgID=" + orgId + "&selectedMarkupID=" + selectedMarkupId;
    openModal(url, "700px", "525px");
}

//Add Account
function goToLookUpOrganization() {
    var url = "/GetTreelineControl.aspx?controlName=/uc/contacts/AddAccount.ascx";
    openModal(url, "500px", "525px");
}

function openContactGroupTagManagement() {
    var selectedContacts = window.getSelectedItems();
    var contactCount = selectedContacts.length;
    if (contactCount === 0) {
        modalAlert(getRes("must_select_one_or_more"));
    } else {
        var url = "/GetTreelineControl.aspx?controlName=/uc/contacts/ContactGroupTagManagement.ascx";
        openModal(url, "300px", "350px");
    }
}

function openContactGroupTagRemoval() {
    var selectedContacts = window.getSelectedItems();
    var contactCount = selectedContacts.length;
    if (contactCount === 0) {
        modalAlert(getRes("must_select_one_or_more"));
    } else {
        var url = "/GetTreelineControl.aspx?controlName=/uc/contacts/ContactGroupTagRemoval.ascx&contactIds=" + selectedContacts;
        openModal(url, "300px", "150px", null, function () {
            reloadList();
            closeModal();
        });
    }
}

function openSharedPeerNotes(sku, orgId, userId, mailingId, canEditNotes) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/SharedPeerNotes.ascx&sku=" + sku + "&orgId=" + orgId + "&userId=" + userId + "&mailingId=" + mailingId + "&canEditNotes=" + canEditNotes;
    openModal(url, "700px", "525px");
}

//Open Add to Collection for All Selected Titles
function openAddtoCollection_Selected() {
    var mailingId = getListViewProperty("selectedMailingID");
    var skuList = window.getSelectedItems();
    var titleCount = skuList.length;
    if (titleCount === 0) {
        modalAlert(getRes("must_select_one_or_more"));
    } else {
        var url = "/GetTreelineControl.aspx?controlName=/uc/catalog/collectionManagementMulti.ascx&titles=" + titleCount;
        openModal(url, "600px", "420px");
    }
    $('#popover_actions').slideUp();
    $('#popover_listView_block').hide();
    $('#popover_analyticsListView_block').hide();
}

function openAddto360Campaign_Selected() {
    var skuList = window.getSelectedItems();
    var titleCount = skuList.length;

    if (titleCount === 0) {
        modalAlert(getRes("must_select_one_or_more"));

        return;
    }

    var url = '/campaigns/bulkAddTitles';

    openModal(url, "700px", "540px");
}

function changeSelectedPublicityCampaignStatus(campaignStatus, dashtype) {
    var campaignList = window.getSelectedItems();
    var campaignCount = campaignList.length;
    if (campaignCount > 0) {
        upsertMultipleEventGridStatus(campaignList, campaignStatus);
        goToListFromDashboard(dashtype);
    }
}

function openAddtoCollection(sku) {
    var mailingId = getListViewProperty("selectedMailingID");
    var url = "/GetTreelineControl.aspx?controlName=/uc/catalog/addToCollection.ascx&sku=" + sku + "&mailingID=" + mailingId;
    openModal(url, "900px", "450px");
}

function openEditPresentation(sku) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/controls/managePresentations.ascx&sku=" + sku;
    openModal(url, "1000px", "600px");
}

function openEditTitle(sku, catalogId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/admin/titleAdminFrame.ascx&sku=" + sku;

    if (catalogId) {
        url += '&catalogId=' + catalogId;
    }

    openChildModal(url, "925px", catalogId ? "650px" : "680px", null, function () {
        ePlus.ui.destroyCkEditorInstance("marketingDescription");
        ePlus.modules.listViewTitleRow.addTitleContentEditedBlocker(sku);
        return true;
    });
}

function openEditTitleInAdminGrid(catalogId, sku, adminView, adminViewSubTab) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/admin/titleAdminFrame.ascx&catalogID=" + catalogId + "&sku=" + sku;

    if (!_.isNil(adminView)) {
        url += "&adminView=" + adminView;
    }

    if (!_.isNil(adminViewSubTab)) {
        url += "&type=" + adminViewSubTab;
    }

    openChildModal(url, "925px", "650px", null, function () {
        ePlus.ui.destroyCkEditorInstance("marketingDescription");
        getProductCatalogAttributes(sku);
        return true;
    });

    $(".modalFrame").zIndex(900007);
}

//Open Personal Tags
function openGenericInput(entityType, parentControl) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/controls/GenericInput.ascx&parentControl=" + parentControl + "&entityType=" + entityType;
    openModal(url, "400px", "150px");
}

function openDrcQuickApprove(action, requestId, userId, sku) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/request/emailRequestProcessing.ascx&action=" + action + "&requestId=" + requestId + "&userId=" + userId + "&sku=" + sku;
    openDialog({
        id: "drcQuickApprove",
        url: url,
        isModal: true,
        isDraggable: false,
        width: "300px",
        height: "200px",
        zIndex: 900006,
        onClose: function () {
            var dashboardType = getEnumValue('dashType', 'DASHDRC');
            var resultType = getEnumValue('resultType', 'TITLEDRCPROCESS_OPEN');
            var onCloseHash = getResultTypeHash(dashboardType, resultType);
            pageChange(onCloseHash);
        }
    });
}

function openDRCProcessRequest(userID, action, requestID) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/request/changeRequestStatus.ascx&source=link&action=" + action + "&users=1&userID=" + userID + "&requestID=" + requestID;
    openModal(url, "600px", "300px");
}

function openDRCProcessAllForUser(userID, action) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/request/changeRequestStatus.ascx&source=link&action=" + action + "&users=1&userID=" + userID;
    openModal(url, "600px", "300px");
}

function openDRCProcessSelected(action) {
    var userList = window.getSelectedItems();
    var userCount = userList.length;
    if (userCount === 0) {
        modalAlert(getRes("must_select_one_or_more"));
    } else {
        var url = "/GetTreelineControl.aspx?controlName=/uc/product/request/changeRequestStatus.ascx&source=select&action=" + action + "&users=" + userCount;
        if (userCount === 1) {
            url = "/GetTreelineControl.aspx?controlName=/uc/product/request/changeRequestStatus.ascx&source=select&action=" + action + "&users=1&userID=" + userList[0];
        }
        openModal(url, "600px", "300px");
    }
    $('#popover_actions').slideUp();
    $('#popover_listView_block').hide();
}

function openDrcRequestModal(requestId, isReadOnly) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/galley/DrcRequestModal.ascx&requestId=" + requestId;
    url += isReadOnly ? "&isReadOnly=" + isReadOnly : "";

    openModal(url, "600px", "500px");
}

function openShareDrcRequestModal(requestId, subject) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/galley/DrcRequestShare.ascx&requestId=" + requestId;
    if (subject) {
        url += "&subject=" + encodeURIComponent(subject);
    }
    openModal(url, "600px", "600px");
}

function openRemoveTagsModal() {
    var skuList = window.getSelectedItems();
    var titleCount = skuList.length;

    if (titleCount === 0) {
        modalAlert(getRes("must_select_one_or_more"));
    } else {
        var url = '/GetTreelineControl.aspx?controlName=/uc/tags/RemoveTags.ascx&skus=' + skuList.join();
        openModal(url, "550px", "150px");
    }

    $('#popover_actions').slideUp();
    $('#popover_listView_block').addClass('hidden');
    $('#popover_analyticsListView_block').addClass('hidden');
}

function openRemoveMarkupTagsModal() {
    var skuList = window.getSelectedItems();
    var titleCount = skuList.length;
    var tagsCountsBySku = getTagsCountsBySku(skuList);

    if (titleCount === 0) {
        modalAlert(getRes("must_select_one_or_more"));
    } else {
        var url = '/GetTreelineControl.aspx?controlName=/uc/markups/RemoveMarkupTags.ascx&skus=' + skuList.join() + '&tagsCountsBySku=' + encodeURIComponent(JSON.stringify(tagsCountsBySku)) + '&mailingId=' + getListViewProperty("selectedMailingID");
        openModal(url, "550px", "150px");
    }

    $('#popover_actions').slideUp();
    $('#popover_listView_block').addClass('hidden');
    $('#popover_analyticsListView_block').addClass('hidden');
}

function getTagsCountsBySku(skuList) {
    var tagsCountsBySku = {};

    for (var i in skuList.reverse()) {
        var sku = skuList[i];

        $("#quickTags_" + sku).find("div[data-tag]").each(function (index, element) {
            tagsCountsBySku[$(this).data().tag] = (tagsCountsBySku[$(this).data().tag] || 0) + 1;
        });
    }

    return tagsCountsBySku;
}

//Open Add Personal Tags for All Selected Titles
function openPersonalTags_Selected() {
    var skuList = window.getSelectedItems();
    var titleCount = skuList.length;
    if (titleCount === 0) {
        modalAlert(getRes("must_select_one_or_more"));
    } else {
        var url = "/GetTreelineControl.aspx?controlName=/uc/tags/tagTitles.ascx&sku=All&titles=" + titleCount + "&quickTagsEnabled=true&tagRootClass=personalTag&tagId=quickPersonalTag";
        openModal(url, "550px", "150px");
    }
    $('#popover_actions').slideUp();
    $('#popover_listView_block').hide();
    $('#popover_analyticsListView_block').hide();
}

function openMarkupTags_Selected() {
    var skuList = window.getSelectedItems();
    var titleCount = skuList.length;
    if (titleCount === 0) {
        modalAlert(getRes("must_select_one_or_more"));
    } else {
        var url = "/GetTreelineControl.aspx?controlName=/uc/tags/tagTitles.ascx&sku=All&titles=" + titleCount + "&quickTagsEnabled=true&tagRootClass=markupTag&tagId=quickTags&mailingId=" + getListViewProperty("selectedMailingID");
        openModal(url, "550px", "150px");
    }
    $('#popover_actions').slideUp(); $('#popover_listView_block').hide();
}

function openSetPriorityOnSelected() {
    var skuList = window.getSelectedItems();
    var titleCount = skuList && skuList.length ? skuList.length : 0;
    if (titleCount === 0) {
        modalAlert(getRes("must_select_one_or_more"));
    } else {
        var url = "/GetTreelineControl.aspx?controlName=/uc/controls/MarkupPriorityModal.ascx&selectedCount=" + titleCount;
        openModal(url, "550px", "150px");
    }
    $("#popover_actions").slideUp(); $("#popover_listView_block").hide();
}

function openHighlightAllCompsForSelected() {
    var skuList = window.getSelectedItems();
    var titleCount = skuList && skuList.length ? skuList.length : 0;
    if (titleCount === 0) {
        modalAlert(getRes("must_select_one_or_more"));
    } else {
        var url = "/GetTreelineControl.aspx?controlName=/uc/controls/HighlightAllCompsModal.ascx&selectedCount=" + titleCount
        openModal(url, "550px", "150px");
    }
    $("#popover_actions").slideUp();
    $("#popover_listView_block").hide();
}

//Open Personal Tags
function openPersonalTags(sku) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/controls/tagTitle.ascx&sku=" + sku;
    openModal(url, "800px", "150px");
}

function openAddCatalogsToFolder() {
    var catalogIds = window.getSelectedItems();

    if (catalogIds.length === 0) {
        modalAlert(getRes("must_select_one_or_more"));
    } else {
        var url = "/GetTreelineControl.aspx?controlName=/uc/controls/AddCatalogsToFolder.ascx";
        openModal(url, "800px", "150px");
    }
}

function openDeleteSelectedCatalogs() {
    var catalogIds = window.getSelectedItems();

    if (catalogIds.length === 0) {
        modalAlert(getRes("must_select_one_or_more"));
    } else {
        deleteCollections(catalogIds);
    }
}

//Open Catalog Tags
function openCatalogTags(catalogId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/controls/tagCatalog.ascx&catalogID=" + catalogId;
    openModal(url, "800px", "150px");
}

//Open Shelves for One Title
function openShelveTitle(sku) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/controls/shelveTitle.ascx&sku=" + sku;
    openModal(url, "600px", "230px");
}

//Edit Advanced Search Name
function editFilter(groupID) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/search/advancedSearch_EditName.ascx&groupID=" + groupID;
    openModal(url, "450px", "180px");
}

//Copy Advanced Search
function openCopyFilter(viewID) {
    if (!viewID) { viewID = $("#selectedFilter").val(); }
    var url = "/GetTreelineControl.aspx?controlName=/uc/search/advancedSearch_Copy.ascx&viewID=" + viewID;
    openModal(url, "480px", "150px");
}

// Promote
function getPromoteModalId(sku) {
    return 'promote-' + sku;
}

function getPromoteModalUrl(sku) {
    return '/GetTreelineControl.aspx?controlName=/uc/promote/promoteMain.ascx&sku=' + sku;
}

function closePromoteModal(sku) {
    var id = getPromoteModalId(sku);
    closeMultiModal(id);
}

function openPromoteModal(sku) {
    var id = getPromoteModalId(sku);

    openMultiModal({
        id: id,
        url: getPromoteModalUrl(sku),
        width: '700px',
        height: '660px',
        onClose: function () {
            closeMultiModal(id);

            if (window.getListViewProperty('reload')) {
                window.setListViewProperty('reload', null);
                reloadList();
            }
        },
        isFixup: true
    });
}

function refreshPromoteModal(sku) {
    var id = getPromoteModalId(sku);
    var url = getPromoteModalUrl(sku);

    $('#pop-modal-inner-' + id).load(url);
}

// Promote - Upload Insert
function getUploadInsertModalId(sku) {
    return 'upload-insert-' + sku;
}

function closeUploadInsertModal(sku) {
    var id = getUploadInsertModalId(sku);
    closeMultiModal(id);
}

function openUploadInsertModal(sku) {
    openMultiModal({
        id: getUploadInsertModalId(sku),
        url: '/GetTreelineControl.aspx?controlName=/uc/promote/uploadInsert.ascx&sku=' + sku,
        width: '600px',
        height: '300px'
    });
}

// Promote - Reserve Featured Spot
function getReserveFeaturedSpotModalId(sku) {
    return 'reserve-featured-spot-' + sku;
}

function openReserveFeaturedSpotModal(sku) {
    openMultiModal({
        id: getReserveFeaturedSpotModalId(sku),
        url: '/GetTreelineControl.aspx?controlName=/uc/promote/reserveFeaturedSpot.ascx&sku=' + sku,
        width: '600px',
        height: '450px'
    });
}

function closeReserveFeaturedSpotModal(sku) {
    var id = getReserveFeaturedSpotModalId(sku);
    closeMultiModal(id);
}

// Promote - Reserve Featured Banner
function getReserveFeaturedBannerModalId() {
    return 'featured-banner';
}

function closeReserveFeaturedBannerModal() {
    var id = getReserveFeaturedBannerModalId();
    closeMultiModal(id);
}

function openReserveFeaturedBannerModal() {
    openMultiModal({
        id: getReserveFeaturedBannerModalId(),
        url: '/promotions/weeklyNewsletterBannerAvailability',
        width: '650px',
        height: '580px'
    });
}

//Promote - Add PRC
function openAddPRC(sku) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/promote/addPRC.ascx&sku=" + sku;
    openModal(url, "600px", "400px");
}

//Promote - Add DRC
function openAddDRC(sku, onClose) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/promote/addDRC.ascx&sku=" + sku;
    openModal(url, "600px", "400px", null, onClose);
}

//Promote - Manage DRC
function openManageDRC(sku) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/galley/promoteReviewCopy.ascx&sku=" + sku;
    openModal(url, "600px", "400px");
}


//Open Personal Note
function openPersonalNote(sku) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/ProductNotes.ascx&sku=" + sku;
    var editor = null;
    var originalNote = '';

    var onLoad = function () {
        try {
            editor = CKEDITOR.instances["text" + sku];
            originalNote = editor.getData();
        } catch (e) {
            console.error(e);
        }
    }

    var onClose = function () {
        if (!editor) {
            return true;
        }

        var note = editor.getData();

        return note === originalNote || confirm(getRes('confirm_close_markup_note'));
    }

    openModal(url, "650px", "350px", onLoad, onClose);
}


//Open Content from Expanded Content area (the glasses)
function showModalExcerpt(sku, controlToLoad) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/controls/readingWindow.ascx&sku=" + sku + "&controlToLoad=" + controlToLoad;
    openModal(url, "90%", "90%");
}

//Open List View Order Previw
function showListViewOrderPreview(orderID) {
    if (orderID == null) {
        orderID = getListViewProperty("itemID");
    }
    var url = "/GetTreelineControl.aspx?controlName=/uc/listviews/ListView_OrderExport_Frame.ascx&resultType=" + getListViewProperty("resultType") + "&dashboardType=" + getListViewProperty("dashboardType") + "&orderID=" + orderID;
    openModal(url, "95%", "95%");
}

////Load Title (newer Version)
function loadModalTitle(sku, source, startingContent, monthsBack, market, isFamilySkuSelectorShown) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/skuModal.ascx&sku=" + sku + "&source=" + source + "&startingContent=" + startingContent;
    if (typeof isFamilySkuSelectorShown !== "undefined" && isFamilySkuSelectorShown !== null) {
        url += "&isFamilySkuSelectorShown=" + isFamilySkuSelectorShown.toString();
    }
    if (typeof monthsBack !== "undefined" && monthsBack !== null) {
        url += "&monthsBack=" + monthsBack;
    }
    if (typeof market !== "undefined" && market !== null) {
        url += "&market=" + market;
    }
    openModal(url, "920px", "600px");
}

function loadCompTitleModal(targetSkuForMarkupNoteCopy, sku, content) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/skuModal.ascx&sku=" + sku
        + "&source=dash&startingContent=" + content
        + "&targetSkuForMarkupNoteCopy=" + targetSkuForMarkupNoteCopy;

    openModal(url, "920px", "600px");
}

function loadMultiModalTitle(options) {
    var params = {
        sku: options.sku,
        source: options.source,
        startingContent: options.startingContent
    };
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/skuModal.ascx&" + $.param(params);
    openModal(url, "920px", "600px");
};

function loadModalTitleWithNavigation(sku, source, startingContent) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/skuModal.ascx&sku=" + sku + "&source=" + source + "&startingContent=" + startingContent;
    var currentPosition = _.findIndex(window.sortrefine, ["item", sku]) + 1;
    var lastPositon = !_.isNil(window.sortrefine) ? window.sortrefine.length : 0;
    var prevNavigationOffset = function (popModalContentOffsetLeft, $prevNavigation) { return (popModalContentOffsetLeft * (11 / 12)) - $prevNavigation.width() };
    var nextNavigationOffset = function (popModalContentOffsetLeft, windowWidth) { return (windowWidth - popModalContentOffsetLeft) + (popModalContentOffsetLeft * (1 / 12)) };
    var positionContentOffsetTop = function ($popModalContent) { return $popModalContent.offset().top / 2; };

    var navigation = {
        prevNavigation: nextTitleModal.bind(null, sku, -1),
        nextNavigation: nextTitleModal.bind(null, sku, 1),
        prevNavigationOffset: prevNavigationOffset,
        nextNavigationOffset: nextNavigationOffset,
        showPosition: true,
        positionContentOffsetTop: positionContentOffsetTop,
        currentPosition: currentPosition,
        lastPosition: lastPositon
    };

    openModal(url, "920px", "600px", null, null, null, navigation);
}

//Open Create New Markup
function openCreateMarkup(catalogId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/controls/createMarkup.ascx&catalogID=" + catalogId;
    openModal(url, "700px", "400px");
    $('#markupFooterDetail').webuiPopover('hide');
}

//Open Social Sharing
function openSocialSharing(resultType) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/contacts/frames/shareSocial.ascx&referrer=openSharingCenter&resultType=" + resultType;
    openModal(url, "800px", "569px");
}
//Open Share Markup
function openShareMarkup(mailingId, catalogId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/contacts/frames/shareMarkup.ascx&mailingID=" + mailingId + "&catalogID=" + catalogId;
    openModal(url, "750px", "590px");
}
//Open Contact Management
function openContactManagement() {
    var url = "/GetTreelineControl.aspx?controlName=/uc/contacts/frames/contactManagement.ascx&referrer=openUserCenter";
    openModal(url, "770px", "669px");
}

function openImportContacts() {
    var url = "/GetTreelineControl.aspx?controlName=/uc/contacts/ImportContacts.ascx";
    openModal(url, "600px", "180px");
}

//Open Publisher Contact Management
function openPublisherContactManagement() {
    var url = "/GetTreelineControl.aspx?controlName=/uc/contacts/frames/publisherContacts.ascx&referrer=openUserCenter";
    openModal(url, "770px", "569px");
}
//Open Community Management
function openCommunityManagement(comType) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/contacts/frames/communityManagement.ascx&ComType=" + comType + "&referrer=openUserCenter";
    openModal(url, "711px", "569px");
}
//Open Community/People Search
function openCommunitySearch(comType, keywords) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/contacts/frames/communityManagement.ascx&ComType=" + comType + "&keywords=" + encodeURIComponent(keywords).replace(/\s/g, '+');
    openModal(url, "640px", "570px");
}
//Open Org User Management
function openOrgUserManagement() {
    var url = "/GetTreelineControl.aspx?controlName=/uc/contacts/frames/userManagement.ascx&scrollType=1&referrer=openUserCenter";
    openModal(url, "711px", "569px");
}
function createNewFilter() {
    openAdvancedSearch("new=true");
}
//Open Advanced Search
function openAdvancedSearch(params) {
    if (_.isNil(params)) {
        var viewId = getListViewProperty("itemID");
        params = "viewID=" + viewId;
    };
    var url = "/GetTreelineControl.aspx?controlName=/uc/search/advancedSearchV2.ascx&" + params;
    openModal(url, "920px", "550px");
}

//Open Preferences
function openPreferencesWindow(startingPref) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/preferences/Preferences_Main.ascx&startingPref=" + startingPref;
    openModal(url, "920px", "640px", null, function () {
        if ($('#saveMappingsIcon').is(':visible') && confirm(getRes('ask_mapping_save_v2'))) {
            saveMappings(function () {
                closeModal();
            });

            return false;
        } else {
            return true;
        }
    });
}

function showModalFriendRequest(friendId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/contacts/AddFriend.ascx&friendID=" + friendId;
    openModal(url, "800px", "500px");
}

function openOrderSum(sku) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/orders/OrderSKUSummary.ascx&sku=" + sku;
    openModal(url, "600px", "400px");
}


//Top Header Function
function openMessageCenter(noticeType, laneID) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/notices/noticeCenter.ascx&noticeType=" + noticeType + "&laneID=" + laneID;
    openModal(url, "800px", "500px");
}
function openUserCenter(openTo, userId) {
    closeModal();
    var url = "/GetTreelineControl.aspx?controlName=/uc/user/admin/userProfileFrame.ascx&openTo=" + openTo;

    if (userId) {
        url += "&userId=" + userId;
    }
    openModal(url, "900px", "590px");
}

function openUserSocial(appId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/user/userSocial.ascx&appId=" + appId;
    openModal(url, "850px", "600px");
}

function openUserSupport(appUserId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/user/userSupportPage.ascx&appUserID=" + appUserId;
    openMultiModal({
        id: 'user',
        url: url,
        width: "750px",
        height: "600px"
    });
}

function openAccountSupport(orgID, startMenu) {
    var startMenuString = "";
    if (startMenu) {
        startMenuString = "&startMenu=" + startMenu
    }
    var url = "/GetTreelineControl.aspx?controlName=/uc/organization/accountSupportPage.ascx&orgID=" + orgID + startMenuString;
    openMultiModal({
        id: 'account',
        url: url,
        width: "750px",
        height: "600px"
    });
}

function openUserContact(contactId, resultType, laneID, widgetID) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/user/userContact.ascx&contactId=" + contactId + "&resultType=" + resultType + "&laneID=" + laneID + "&widgetID=" + widgetID;

    openModal(url, "450px", "500px", null, function () {
        if (resultType === getEnumValue("resultType", "PEOPLE_CONTACTS_INDIVIDUALS") && getListViewProperty('doReload')) {
            reloadCurrentPage();
        } else {
            closeModal();
        }

        return true;
    });
}

function openProfilePicEditor() {
    var url = "/uc/user/EditProfilePicture.aspx";
    openModal(url, "400px", "300px", null, function () {
        if (ias) {
            ias.cancelSelection();
            ias.remove();
        }

        return true;
    });
}

function openProfilePicUploader() {
    var url = "/uc/user/UploadProfilePicture.aspx";
    openModal(url, "400px", "200px");
}

function openSavedSearches() {
    var url = "/GetTreelineControl.aspx?controlName=/uc/dashboard/item_Options_Search.ascx";
    openModal(url, "600px", "400px");
}

function openMyEdelweissSetup() {
    var url = "/GetTreelineControl.aspx?controlName=/uc/preferences/myEdelweissSetup.ascx";
    openModal(url, "700px", "440px");
}

function openColorKey() {
    var url = "/GetTreelineControl.aspx?controlName=/uc/help/ColorKey.ascx";
    openModal(url, "310px", "180px");
}

//Open Dashboard Options
function toggleDashboardOptions(dashType, resultType) {
    if ($("#popIcon_" + dashType).hasClass("icon-drop-down-icon")) {
        $("#popIcon_" + dashType).removeClass("icon-drop-down-icon");
        $("#popIcon_" + dashType).addClass("icon-drop-up-icon-01");
        var url = "/GetTreelineControl.aspx?controlName=/uc/dashboard/DashboardOptions.ascx&dashType=" + dashType + "&resultType=" + resultType;
        $("#dashOptions_" + dashType).load(url, function () {
            $("#dash_" + dashType).css("max-height", "");
            $("#dash_" + dashType).css("height", "");
            $("#dashOptions_" + dashType).slideDown();
        });
    } else {
        $("#popIcon_" + dashType).removeClass("icon-drop-up-icon-01");
        $("#popIcon_" + dashType).addClass("icon-drop-down-icon");
        $("#dashOptions_" + dashType).slideUp();
    }
}

//Open Dashboard Options
function openSharingCenter() {
    var url = "/GetTreelineControl.aspx?controlName=/uc/sharing/SharingMain.ascx";
    openModal(url, "700px", "300px");
}


//Open Presentation Mode
function showPresentation(sku) {
    //$("#titleSection").html("");
    var querystring = ""
    if (sku != undefined) {
        querystring += "&sku=" + sku
    }
    var url = "/GetTreelineControl.aspx?controlName=/uc/listviews/PresentationView.ascx" + querystring;
    openModalFull(url, "100%", "100%", 1);
}

//Open Publisher View
function showPublisher(orgId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/dashboard/DashboardFramePublishers.ascx&OrgID=" + orgId;
    openModalFull(url, "100%", "100%", 0);
}

function openCreateCollection() {
    var url = "/GetTreelineControl.aspx?controlName=/uc/catalog/newCollectionModal.ascx";
    openModal(url, "450px", "200px");
}

function openCreateOrder(event, catalogID) {
    if (event && _.isFunction(event.stopPropagation)) {
        event.stopPropagation();
    }
    WebuiPopovers.hideAll();
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/editOrder.ascx";
    if (catalogID) {
        url += "&catalogID=" + catalogID;
    }
    openModal(url, "900px", "600px");
}
function openEditOrder(orderId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/editOrder.ascx&orderID=" + orderId;
    openModal(url, "900px", "600px");
}

function openShareOrder(orderId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/contacts/frames/shareOrder.ascx&orderID=" + orderId;
    openChildModal(url, "732px", "565px");
}

function openEditPromoCode(orderId, exportType) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/orders/OrderPromoCodeEntry.ascx&orderId=" + orderId + "&exportType=" + exportType;
    openChildModal(url, "400px", "150px");
}

function openSearchAll(keywords) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/search/searchAll.ascx&keywords=" + keywords;
    openModal(url, "500px", "220px");
}

function openTimeSelector(resultType, dashboardType) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/controls/TimeSelectorPop.ascx&resultType=" + resultType;
    if (dashboardType != null)
        url += "&dashboardType=" + dashboardType;

    openChildModal(url, "300px", "340px");
}

function openEditCatalog(catalogId, resultType, options) {
    closeModal();
    var url = "/GetTreelineControl.aspx?controlName=/uc/catalog/ManageCatalogOrder.ascx&catalogID=" + catalogId + "&resultType=" + resultType;

    if ($("#editTitleDiv").length > 0) {
        var loading = getRes('loading');

        closeSavingModalOverlay("productAdminGrid");
        savingModalOverlay(loading, 'productAdminGrid');

        var $showcaseMenuButtons = $('a.showcaseMenuButton', '#products');

        $showcaseMenuButtons.webuiPopover('hide');
        $showcaseMenuButtons.webuiPopover('destroy');

        $(".ePlusDialogContent").load(url, function () {
            closeSavingModalOverlay("productAdminGrid");
        });
    } else {
        closeSavingModalOverlay("productAdminGrid");
        openDialog({
            id: "editTitleDiv",
            url: url,
            isModal: true,
            isDraggable: false,
            width: "95%",
            height: "95%",
            zIndex: 900006,
            onShow: function () {
                resizeManageCatalogOrderGrid();
            },
            onBeforeClose: function () {
                WebuiPopovers.hideAll();
            },
            onClose: function () {
                $(window).off('resize.adminGrid');

                if (options && typeof options.onClose === 'function') {
                    options.onClose();
                } else {
                    if (getListViewProperty("dashboardType") === getEnumValue("dashType", "DASHCATALOGTITLES")) {
                        var createCollectionEvent = new CustomEvent("refreshCatalogDataAfterCollectionCreation", {
                            detail: {
                                didSucceed: true
                            }
                        });
                        window.dispatchEvent(createCollectionEvent)
                        reloadCurrentPage();
                    }
                }
            }
        });
    }
}

function openImportMarkup(catalogId, orgId) {
    var markupId = getListViewProperty("selectedMailingID");
    var url = "/GetTreelineControl.aspx?controlName=/uc/catalog/importMarkup.ascx&catalogID=" + catalogId + "&markupID=" + markupId + "&org=" + orgId;
    openModal(url, "600px", "280px");
}

function openImportCompSales(catalogId, orgId) {
    var markupId = getListViewProperty("selectedMailingID");
    var url = "/GetTreelineControl.aspx?controlName=/uc/catalog/importCompSales.ascx&catalogID=" + catalogId + "&markupID=" + markupId + "&orgID=" + orgId;
    openModal(url, "500px", "230px");
}


function openExportMarkup(catalogId, orgId, sortColumn) {
    function exportMarkup(doExportAsText, $dialog) {
        var markupId = getListViewProperty("selectedMailingID");

        window.open("/uc/catalog/ExportExcel.aspx?mailingID=" + markupId + "&catalogID=" + catalogId +
            "&org=" + orgId + "&text=" + doExportAsText.toString() +
            "&sord=" + (sortColumn ? sortColumn : ""), "export");

        if ($dialog) {
            $dialog.trigger("close");
        }
    }

    openDialog({
        title: getRes('export_markup'),
        content: getRes('export_markup_prompt'),
        isModal: true,
        doAutoOpen: true,
        buttons: [
            {
                text: getRes('yes'),
                onClick: function (event, dialog) {
                    exportMarkup(false, dialog);
                }
            },
            {
                text: getRes('no'),
                onClick: function (event, dialog) {
                    exportMarkup(true, dialog);
                }
            }
        ],
    });
}

function openExportReview(resultType) {
    var reviewList = window.getSelectedItemsIntoString();
    var reviewCount = reviewList.length;
    if (reviewCount === 0) {
        modalAlert(getRes("must_select_one_or_more"));
    } else {
        $.post("/getJSONData.aspx?m=SocialNetwork&builder=SaveSelectionToSession",
            {
                entityType: 'reviewList',
                selectedList: reviewList
            },
            function (data) {
                window.open("/exports/ExportReviews.aspx?resultType=" + resultType, "export");
            }, "json");
        return false;
    }
}

function openExportSupplierEventGridUsers(supplierOrgId) {
    window.open("/exports/ExportSupplierEventGridUsers.aspx?supplierOrgId=" + supplierOrgId, "export");
    return false;
}

function openExportProcessedRequests() {
    var requestList = window.getSelectedItemsIntoString();
    var requestCount = requestList.length;
    if (requestCount === 0) {
        modalAlert(getRes("must_select_one_or_more"));
    } else {
        $.post("/getJSONData.aspx?m=SocialNetwork&builder=SaveSelectionToSession",
            {
                entityType: 'requestList',
                selectedList: requestList
            },
            function (data) {
                window.open("/exports/ExportProcessedRequests.aspx", "export");
            }, "json");
        return false;
    }
}

function openAddProducts(catalogId, resultType) {
    $dialog.trigger('showBlock');

    var url = "/GetTreelineControl.aspx?controlName=/uc/catalog/AddProductsToCatalog.ascx&catalogID=" + catalogId + "&resultType=" + resultType;

    openModal(url, "600px", "450px", null, function () {
        $dialog.trigger('hideBlock');
        closeModal();
    });
}

function openWelcomeOverview() {
    var url = '/GetTreelineControl.aspx?controlName=/uc/welcome/Welcome_Overview.ascx';
    openModal(url, '700px', '100%');
}

function openSingleReview(appUserId, sku) {
    var url = '/GetTreelineControl.aspx?controlName=/uc/social/singleReview.ascx&userID=' + appUserId + '&sku=' + sku;
    openMultiModal({
        id: 'single-review',
        url: url,
        width: '700px',
        height: '430px'
    });
}

function openReviewByID(assessmentID) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/social/singleReview.ascx&assessmentID=" + assessmentID;
    openChildModal(url, "700px", "430px");
}

function loadContactQuickAdd(groupId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/contacts/scrollers/contactScrollerQuickAdd.ascx&groupId=" + groupId;
    openChildModal(url, "420px", "490px");
}

function loadSharedMarkupsByUser(orgId, userId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/controls/sharedMarkupsbyUser.ascx&orgId=" + orgId + "&userId=" + userId;
    openChildModal(url, "590px", "500px");
}

function LoadMuchLove(sku) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/MuchLove.ascx&sku=" + sku;
    openModal(url, "740px", "400px");
}

function RequestGalley(sku, requestType, requestFormat) {
    if (_.isNil(requestFormat)) {
        requestFormat = 0;
    }
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/request/reviewCopyRequest.ascx&sku=" + sku + "&requestType=" + requestType + "&requestFormat=" + requestFormat;
    openModal(url, "700px", "455px");
}

function openDRCProcessing() {
    var url = "/GetTreelineControl.aspx?controlName=/uc/controls/DRCProcessing.ascx";
    openModal(url, "900px", "700px");
}

function DownloadGalley(sku, formatType, formatsSelectionForm, continueWithoutPreference) {
    var parameterContinueWithoutPreference = "";

    if (continueWithoutPreference) {
        parameterContinueWithoutPreference = "&doDownloadWithoutPreference=" + continueWithoutPreference;
    }

    var url = "/GetTreelineControl.aspx?controlName=/uc/product/download/two_Download.ascx&sku=" + sku + "&formatType=" + formatType + parameterContinueWithoutPreference;
    openModal(url, "750px", "450px", null, null, formatsSelectionForm);
}

function reloadDrcActions(sku) {
    var $drcArea = $('#drcArea_' + sku);
    var $drcAction = $('#drcAction_' + sku);

    if ($drcArea && $drcArea.length > 0) {
        var url = "/GetTreelineControl.aspx?controlName=/uc/listviews/controls/DrcActions.ascx&accessType=1&sku=" + sku;

        $.ajax({
            type: 'GET',
            url: url
        }).done(function (html) {
            $drcArea.replaceWith(html);
            initDrcActions(sku);
        });
    }

    if ($drcAction && $drcAction.length > 0) {
        var url = "/GetTreelineControl.aspx?controlName=/uc/listviews/controls/DrcActionStrip.ascx&accessType=1&sku=" + sku;

        $.ajax({
            type: 'GET',
            url: url
        }).done(function (html) {
            $drcAction.replaceWith(html);
            initDrcActions(sku);
        });
    }
}

function enableGalley(sku) {
    if (!confirm(getRes('enable_request'))) {
        return;
    }

    $.ajax({
        type: 'POST',
        url: '/api/me/galleys/' + sku + '/enable'
    }).done(function () {
        reloadDrcActions(sku);

        adjustResultTypeCount(getEnumValue('resultType', 'TITLEDRCREQUESTOPEN'), 1);
        adjustResultTypeCount(getEnumValue('resultType', 'TITLEDRCREQUESTCANCELLED'), -1);

        closeModal();
    }).fail(function (ret) {
        if (ret) {
            var json = JSON.parse(ret.responseText);
            var message = json.message;

            alert(message);
        }
    });
}

function cancelGalley(sku) {
    if (!confirm(getRes('cancel_request'))) {
        return;
    }

    $.ajax({
        type: 'POST',
        url: '/api/me/galleys/' + sku + '/cancel'
    }).done(function () {
        reloadDrcActions(sku);

        adjustResultTypeCount(getEnumValue('resultType', 'TITLEDRCREQUESTOPEN'), -1);
        adjustResultTypeCount(getEnumValue('resultType', 'TITLEDRCREQUESTCANCELLED'), 1);

        closeModal();
    }).fail(function (ret) {
        if (ret) {
            var json = JSON.parse(ret.responseText);
            var message = json.message;

            alert(message);
        }
    });
}

function addNewCatalogFolder(folderId, parentType, targetGroupId, level) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/organization/createFolder.ascx&parentKey=" + folderId + "&parentType=" + parentType + "&targetGroupID=" + targetGroupId + "&level=" + level;
    openModal(url, "700px", "350px");
}

function editCatalogFolder(folderId, parentType, targetGroupId, level, parentFolderId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/organization/manageFolder.ascx&folderID=" + folderId + "&parentType=" + parentType + "&targetGroupID=" + targetGroupId + "&level=" + level + "&parentFolderId=" + parentFolderId;
    openModal(url, "800px", "600px");
}

function ShowAddPageDialog() {
    var url = "/GetTreelineControl.aspx?controlName=/uc/content/AddEditPage.ascx";
    openModal(url, "390px", "130px");
}
function ShowEditPageDialog() {
    var contentName = $('#contentID').val();
    var contentId = $('#contentName').val();
    var url = "/GetTreelineControl.aspx?controlName=/uc/content/AddEditPage.ascx&contentName=" + contentName + "&contentID=" + contentId;
    openModal(url, "390px", "130px");
}

function openFindIt(dashboardType) {
    if (dashboardType == 2) {
        var urlCatalog = "/GetTreelineControl.aspx?controlName=/uc/catalog/CatalogQuickSearchWrapper.ascx";
        openModal(urlCatalog, "600px", "160px");
    }
    if (dashboardType == 4) {
        var urlPublisher = "/GetTreelineControl.aspx?controlName=/uc/organization/PublisherQuickSearch.ascx";
        openModal(urlPublisher, "600px", "130px");
    }
    if (dashboardType == 5) {
        var urlPeople = "/GetTreelineControl.aspx?controlName=/uc/social/PersonQuickSearch.ascx";
        openModal(urlPeople, "600px", "130px");
    }
}

function openPersonReviewFindIt(appId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/social/PersonReviewQuickSearch.ascx&appId=" + appId;
    openModal(url, "600px", "130px");
}

function getTurboReviewControlName() {
    return window.hasCommunitiesPrerelease ? "reviewTitleTurbo" : "reviewTitleTurboOld";
}

function openTurboReview(sku) {
    openDialog({
        id: "reviewTitleTurbo_" + sku,
        url: "/GetTreelineControl.aspx?controlName=/uc/controls/" + getTurboReviewControlName() + ".ascx&sku=" + sku,
        isModal: true,
        isDraggable: false,
        zIndex: 9000000,
        onShow: function () {
            ePlus.modules.community.api.exception.getCommunityReviewShareCount(sku)
                .done(function (reviewShareCount) {
                    $('#community-share-count').html(
                        reviewShareCount.toLocaleString(ePlus.user.culture));
                })
                .fail(function () {
                    $('#community-share-count').html('-');
                });
        },
        height: "505px",
        width: "785"
    });
}

function openManageMyPublishers() {
    var url = "/GetTreelineControl.aspx?controlName=/uc/user/manageMyPublishers.ascx";
    openModal(url, "400px", "560px");
}

function openCatalogSearchInPublisherPage(orgId, source) {
    var urlCatalog = "/GetTreelineControl.aspx?controlName=/uc/catalog/CatalogQuickSearchWrapper.ascx&orgID=" + orgId + "&source=" + source;
    openChildModal(urlCatalog, "600px", "160px");
}

function openAddCatalogToFolder(orgId, source, folderId) {
    var urlCatalog = "/GetTreelineControl.aspx?controlName=/uc/catalog/CatalogQuickSearchWrapper.ascx&orgID=" + orgId + "&source=" + source + "&folderID=" + folderId;
    openChildModal(urlCatalog, "600px", "160px");
}

function AddEditCatalog(catalogId, folderId, catalogType) {
    const catalog = 1, collection = 2;
    var url = "/GetTreelineControl.aspx?controlName=/uc/catalog/CatalogManage.ascx&catalogID=" + catalogId + "&folderID=" + folderId;

    if (catalogType)
        url += "&catalogType=" + catalogType;

    openModal(url, "690px", "450px", null, (catalogType == catalog && catalogId != 0) ? AddEditCatalogIsFolderSet : null);
}

function AddEditCatalogIsFolderSet() {
    const folders = $("#selectedFolderIDs").val();

    if (!folders || folders === "0") {
        alert(getRes("select_folder"));

        return false;
    }

    return true;
}

function openCreateCover(catalogId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/catalog/SelectCovers.ascx&catalogID=" + catalogId;
    openChildModal(url, "690px", "430px");
}

function openCustomerFeedback(isPull) {
    WebuiPopovers.hideAll();
    var url = "/GetTreelineControl.aspx?controlName=/uc/feedback/CustomerFeedback.ascx" + (isPull ? "&type=pull" : "");
    openModal(url, "580px", "325px");
}

function openCustomerFeedbackRedirect(event) {
    event.preventDefault();
    WebuiPopovers.hideAll();
    var url = "/GetTreelineControl.aspx?controlName=/uc/feedback/CustomerFeedback.ascx&isRedirect=true";
    openModal(url, "580px", "310px");
}

function openCompGraph(sku) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/CompsGraphDetail.ascx&sku=" + sku;
    openModal(url, "700px", "590px");
}

function openCompSearch(sku, source) {
    if ($("#comp-search-" + sku).length > 0) {
        return;
    }

    var url = "/GetTreelineControl.aspx?controlName=/uc/product/CompsSearch.ascx&sku=" + sku + "&source=" + source;

    openMultiModal({
        id: "comp-search-" + sku,
        url: url,
        width: "600px",
        height: "450px",
        isFixup: true
    });
}

function closeCompSearch(sku) {
    closeMultiModal('comp-search-results-' + sku);
    closeMultiModal('comp-search-' + sku);
}

function openCompHidden(sku, mailingID) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/CompsHidden.ascx&sku=" + sku + "&mailingID=" + mailingID;
    openModal(url, "800px", "600px");
}

function openFamilyMembershipManagementModal(sku) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/analytics/FamilyMembershipManagement.ascx&sku=" + sku;
    openMultiModal({
        id: "familyMembershipManagementModal",
        url: url,
        width: "800px",
        height: "350px",
        isFixup: true
    });
}

function openScorecardRunProgressModal(jobID) {
    var params = {
        jobID: jobID
    };
    var url = "/GetTreelineControl.aspx?controlName=/uc/analytics/ScorecardRunProgress.ascx&" + $.param(params);
    openMultiModal({
        id: 'scorecardRunProgress',
        url: url,
        width: "800px",
        height: "600px"
    });
};

function openTagManagement(resultType) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/controls/manageTags.ascx&resultType=" + resultType;
    openModal(url, "600px", "555px");
}

function openOrderNotification(orderId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/orders/OrderNotification.ascx&orderId=" + encodeURIComponent(orderId);
    openModal(url, "700px", "520px");
}

function openLocationSelect(entityType) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/controls/LocationSelect.ascx&entityType=" + entityType;
    openModal(url, "700px", "500px");
}

function addEditContributor(ContributorID, selectedContributorType, sku) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/admin/titleAdmin_Contributor_AddEdit.ascx&ContributorID=" + ContributorID + "&selectedContributorType=" + selectedContributorType + "&sku=" + sku;
    openModal(url, "700px", "250px");
}

function addEditPrice(selectedPriceOption, sku) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/admin/titleAdmin_Prices_AddEdit.ascx&selectedPriceOption=" + selectedPriceOption + "&sku=" + sku;
    openModal(url, "700px", "250px");
}

function addEditLink(linkID, selectedLinkType, selectedLinkSubType, sku) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/admin/titleAdmin_Links_AddEdit.ascx"
        + "&linkID=" + linkID
        + "&selectedLinkType=" + selectedLinkType
        + "&selectedLinkSubType=" + selectedLinkSubType
        + "&sku=" + sku;
    openModal(url, "700px", "450px");
}

function addRelatedProduct(sku) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/admin/titleAdmin_RelatedProducts_Add.ascx&sku=" + sku;
    openModal(url, "700px", "350px");
}

function addCategory(type, sku) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/admin/titleAdmin_Categories_Select.ascx&type=" + type + "&sku=" + sku;
    openModal(url, "500px", "500px");
}

function addEditBurst(burstID, selectedBurstType, sku) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/admin/titleAdmin_Images_Bursts_AddEdit.ascx&burstID=" + burstID + "&selectedBurstType=" + selectedBurstType + "&sku=" + sku;
    openModal(url, "600px", "250px");
}

function openUploadGeneric(processingHandler, extraQSControl, completeFunction, uploadHead) {
    var url = "/uc/controls/UploadGeneric.aspx?processingHandler=" + processingHandler + "&extraQSControl=" + extraQSControl + "&completeFunction=" + completeFunction + "&uploadHead=" + uploadHead;
    openModal(url, "500px", "350px");
}

function openLinkEdit(appUserID) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/user/userLinkEditFrame.ascx&appUserID=" + appUserID;
    openModal(url, "500px", "330px");
}

function openNewUserPop(orgID) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/user/addNewUser.ascx&orgID=" + orgID
    openModal(url, "680px", "570px");
}

function openUserPrivileges(appUserID) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/user/selectPrivileges.ascx&appUserID=" + appUserID;
    openModal(url, "500px", "400px");
}
function openUserPrivilegesForNewUser(emailAdd) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/user/selectPrivileges.ascx&emailAdd=" + emailAdd + "&isNewUser=true";
    openModal(url, "500px", "400px");
}
function openUserRoles(orgID, userID) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/user/selectRoles.ascx&orgID=" + orgID + "&userID=" + userID;
    openModal(url, "500px", "400px");
}

function openMarketSelector(source) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/header/two_Header_MarketSelect.ascx&source=" + source;
    openModal(url, "700px", "490px");
}

function openSetPassword(source, t, email) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/user/setPassword.ascx&source=" + source + "&t=" + t + "&email=" + email;
    openModal(url, "540px", "290px");
}
function openForgotPassword(email) {
    var url
    if (email != null) {
        url = "/GetTreelineControl.aspx?controlName=/uc/user/forgotPassword.ascx&email=" + email;
    } else {
        url = "/GetTreelineControl.aspx?controlName=/uc/user/forgotPassword.ascx";
    }

    openModal(url, "540px", "240px");
}

function manageAffiliations(appUserID) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/user/manageAffiliations.ascx&appUserID=" + appUserID;
    openModal(url, "640px", "480px");
}

function openUserInfoEdit(appUserID) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/user/userInfoInput.ascx&appUserID=" + appUserID;
    openModal(url, "440px", "330px");
}

function openContactInfoEdit() {
    var url = "/GetTreelineControl.aspx?controlName=/uc/user/userContactInput.ascx";
    var data = {
        email: $("#cSelected_email").html(),
        firstName: $("#cSelected_firstName").html(),
        lastName: $("#cSelected_lastName").html(),
        account: $("#cSelected_orgName").html(),
        phone: $("#cSelected_phone").html(),
        city: $("#cSelected_city").html(),
        state: $("#cSelected_state").html()
    }

    openModal(url, "440px", "330px", null, null, data);
}

function openAccountInfoEdit(appUserId, onClose) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/user/accountInfoInput.ascx&appUserID=" + appUserId;
    openModal(url, "540px", "440px", null, onClose);
}

function openAccountInfoEdit_orgID(orgID) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/user/accountInfoInput.ascx&orgID=" + orgID;
    openModal(url, "540px", "440px");
}

function EditExportRecord(orderId, row, sku, sortBy, sortDirection, protectedView, mode, referenceID) {
    var isExportGridView = $('#exportType').length > 0;
    if (isExportGridView) {
        var exportInfo = $('#exportType').attr("val").split("~");
        var exportType = exportInfo[0];
        var reportname = exportInfo[1];
    }
    $.url = '/GetTreelineControl.aspx?controlName=/uc/product/editOrderExportRecord.ascx&lineNumber=' + row;

    if (orderId) { $.url = $.url + "&orderID=" + orderId; }
    if (exportType) { $.url = $.url + "&exportType=" + exportType; }
    if (reportname) { $.url = $.url + "&reportname=" + encodeURIComponent(reportname); }
    if (sortBy) { $.url = $.url + "&sortBy=" + encodeURIComponent(sortBy); }
    if (sortDirection) { $.url = $.url + "&sortDirection=" + encodeURIComponent(sortDirection); }
    if (sku) { $.url = $.url + "&sku=" + encodeURIComponent(sku); }
    if (mode) { $.url = $.url + "&mode=" + encodeURIComponent(mode); }
    if (referenceID) { $.url = $.url + "&referenceID=" + encodeURIComponent(referenceID); }

    $.url = $.url + "&protected=" + protectedView;
    $.url += "&isExportGridView=" + isExportGridView.toString();

    $.title = getRes("edit_export_record");

    if (row == -1) {
        $.title = getRes("add_export_record");
    }

    if (mode == 1)
        $.title = getRes("add_line_item");

    openChildModal($.url, "700px", "540px");
}

function openExportDownloadOptions(exportType, downloadType, height) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/orders/ExportDownloadOptions.ascx&exportType=" + exportType;

    if (downloadType) {
        url += "&downloadType=" + downloadType;
    }
    openChildModal(url, "500px", height || "220px");
}

function openContentEditor(contentId) {
    openModal("/GetTreelineControl.aspx?controlName=/uc/controls/ContentEditor.ascx&contentId=" + contentId, "400px", "300px");
}

function loadBertrams() {
    var url = "/GetTreelineControl.aspx?controlName=/uc/support/bertrams.ascx";
    openModal(url, "900px", "600px");
}

function openResequenceCatalogUserWorklist() {
    var url = "/GetTreelineControl.aspx?controlName=/uc/controls/ResequenceCatalogUserWorklist.ascx";

    openModal(url, "900px", "600px");
}

function openPublicityCampaignEventRequest(campaignId, eventId, sku, commentType, readonly) {
    var queryString = '&campaignID=' + campaignId + '&eventID=' + eventId + '&sku=' + sku + '&commentType=' + commentType + '&readonly=' + readonly;
    var url = "/GetTreelineControl.aspx?controlName=/uc/PublicityCampaign/PublicityCampaign_Request_Editor.ascx" + queryString;
    openModal(url, "700px", "450px");
}

function isInGoToListMode() {
    return $(".advSearchGoToList").is(":visible");
}

function switchToGoToListMode() {
    $(".advSearchCustomCategory").hide();
    $(".advSearchGoToList").show();
}

function switchToCustomCategoryMode() {
    $(".advSearchCustomCategory").show();
    $(".advSearchGoToList").hide();
}

function newFilter() {
    $("#savedFilterHeader").hide();
    $("#newFilterDiv").show();
    $("#selectedFilter").val(0);

    //if a pos or custom category is selected in the dropdown attribute list, show the warning message
    if (EdelweissAnalytics.customCategoryTypes.indexOf($("#categoryType").attr('val')) >= 0) {
        switchToCustomCategoryMode();
    }
    else {
        switchToGoToListMode();
    }

    $.url = "/GetTreelineControl.aspx?controlName=/uc/search/advancedSearch_filterRender.ascx&viewID=0";
    $("#filterDisplay").load($.url, function () {
        UpdateDefaultView();
    });
}

//Unsaved filters are still saved to the database. This function does that
function UpdateDefaultView() {
    $.post("/getJSONData.aspx?builder=SaveUpdateRefinementsAsDefaultView", $('#selected_Filters :input').serialize(), function (data) {
        if (data.code !== "ERROR" && isInGoToListMode()) {
            getResultCount();
        }
    });
}

function getResultCount() {
    $("#resultCount").html("---");

    var viewId = $("#selectedFilter").val();

    if (viewId === "-1") {
        $("#selectedFilter").val(0);
    }

    var url = "/GetTreelineControl.aspx?controlName=/uc/search/advancedSearch_Results.ascx&viewID=" + viewId;

    $("#resultCount").load(url, function () {
        $(".goToList").show();

        if (viewId > 0) {
            $("#newFilterDiv").hide();
        }

        window.closeSavingModal();
    });
}

function openSelectMarkupsModal(collection, sku, mailingId, message) {
    var queryString = '&markups=' + collection + '&sku=' + sku + '&mailingId=' + mailingId + '&message=' + encodeURIComponent(message);
    var url = "/GetTreelineControl.aspx?controlName=/uc/controls/selectMarkups.ascx" + queryString;
    openModal(url, "900px", "600px");
}

function openOrderNotificationHistoryModal(orderId, repView) {
    var url = '/GetTreelineControl.aspx?controlName=/uc/orders/OrderNotificationHistory.ascx&orderId=' + orderId + '&repView=' + repView;
    openModal(url, "900px", "600px");
}

function openChangeEmailAddressModal(appUserID) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/user/ChangeUserEmail.ascx&appUserID=" + appUserID;
    openModal(url, "440px", "150px");
}

function openTemplateOptions(templateId, templateStrId, templateType, sku, output) {
    var url = '/GetTreelineControl.aspx?controlName=/uc/controls/exportTitles_Options.ascx&templateId=' + templateId + '&templateStrId=' + templateStrId + '&templateType=' + templateType + '&sku=' + sku + '&output=' + output;
    openDialog({
        id: "exportTitlesPopup",
        url: url,
        isDraggable: false,
        width: "900px",
        height: "600px",
        zIndex: $('#printDialog').zIndex() + 1
    });
}

function openChangePasswordModal(appUserID) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/user/ChangeUserPassword.ascx&appUserID=" + appUserID;
    openModal(url, "480px", "200px");
}

function openIsbnCatalogSearch() {
    var url = "/GetTreelineControl.aspx?controlName=/uc/search/IsbnCatalogSearch.ascx";

    openDialog({
        id: "isbnCatalogSearch",
        url: url,
        isDraggable: false,
        width: "920px",
        height: "700px",
        zIndex: 900003
    });
}

function openIsbnCatalogNuke() {
    var url = "/GetTreelineControl.aspx?controlName=/uc/catalog/CatalogISBNNuke.ascx";

    openDialog({
        id: "CatalogIsbnNuke",
        url: url,
        isDraggable: false,
        width: "750px",
        height: "500px",
        zIndex: 900003
    });
}

function openShareReviewCopyModal(sku) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/galley/shareReviewCopy.ascx&sku=" + sku;
    openModal(url, "800px", "650px");
}

function openPublicityCampaignsRequest(sku, campaignId, comesFrom, group) {
    var doOnlyShowMyAccounts = "False";

    if ($("#campaignViewJustMyAccounts").hasClass("box_checked")) {
        doOnlyShowMyAccounts = "True";
    }

    var url = "/GetTreelineControl.aspx?controlName=/uc/PublicityCampaign/PublicityCampaign_Requests.ascx&sku=" +
        sku + "&doOnlyShowMyAccounts=" + doOnlyShowMyAccounts + "&comesFrom=" + comesFrom + "&group=" + group + "&campaignId=" + campaignId;

    openModal(url, "750px", "600px");
}

function openPublicityLinksWindow(eventId, sku) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/PublicityCampaign/Publicity_Links.ascx&eventId=" + eventId + "&sku=" + sku;
    openModal(url, "300px", "300px");
}

function openEditMarkupDetails(mailingId, catalogId) {
    $('#markupFooterDetail').webuiPopover('hide');
    var url = "/GetTreelineControl.aspx?controlName=/uc/controls/editMarkup.ascx&mailingID=" + mailingId + "&catalogID=" + catalogId;
    openModal(url, "650px", "550px");
}
function openEditMarkupOverviewMessage(mailingId, catalogId) {
    $('#markupFooterDetail').webuiPopover('hide');
    var url = "/GetTreelineControl.aspx?controlName=/uc/controls/editMarkup.ascx&mailingID=" + mailingId + "&catalogID=" + catalogId + "&doShowMarkupNameEdit=false";
    openModal(url, "650px", "250px");
}

function openExportSelectedOrders() {
    var selectedOrders = window.getSelectedItems();

    if (_.isNil(selectedOrders) || selectedOrders.length == 0) {
        modalAlert(getRes("must_select_one_or_more"));
        return;
    }
    var data = {
        csvOrderString: selectedOrders.join(",")
    }

    var url = "/GetTreelineControl.aspx?controlName=/uc/orders/ExportSelectedOrdersModal.ascx&dashboardType=" + getListViewProperty("dashboardType");
    openModal(url, "500px", "360px", null, null, data);
}

function openImportPageNumberModal(catalogId, resultType) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/catalog/ImportPageNumbers.ascx&catalogID=" + catalogId + "&resultType=" + resultType;
    openModal(url, "400px", "150px");
}

function openImportCompTitlesModal(catalogId, sourceOrgId, resultType) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/catalog/ImportCompTitles.ascx&catalogId=" + catalogId + "&sourceOrgId=" + sourceOrgId + "&resultType=" + resultType;
    openModal(url, "400px", "150px");
}

function openImportDescriptionModal(catalogId, resultType) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/catalog/ImportDescription.ascx&catalogId=" + catalogId + "&resultType=" + resultType;
    openModal(url, "400px", "275px");
}

function openCollectionQuickCopy(catalogId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/catalog/CollectionQuickCopy.ascx&catalogId=" + catalogId;
    openModal(url, "750px", "350px");
}

function openEditTourInformation(campaignId, sku) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/PublicityCampaign/PublicityCampaign_TourInfo.ascx&isEditMode=true" + "&campaignID=" + campaignId + "&sku=" + sku;
    openModal(url, "550px", "550px");
}

function openUserLookup() {
    var url = "/GetTreelineControl.aspx?controlName=/uc/contacts/lookUpUser.ascx";
    openModal(url, "500px", "500px");
}

function openOrgContactsModal(orgId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/contacts/details/orgDetail.ascx&orgId=" + orgId;
    openModal(url, "350px", "640px", null, getOrganizationContactCount.bind(null, orgId, updateOrganizationRowContactCount));
}

// open add to plan dialog box
function AddItemsToPlan(skuList, itemList, taskType) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/library/AddToPlan.ascx&taskType=" + (taskType || "");
    var data = {
        skuList: skuList.join(",")
    };
    if (typeof itemList !== "undefined" && itemList !== null) {
        data.itemList = itemList.join(",");
    }
    openModal(url, "670px", "440px", null, null, data);
}

function viewRequestsForGalley(sku, type, format) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/request/ViewReviewCopyRequests.ascx&sku=" + sku + "&type=" + type + "&format=" + format;
    var selectedOrgId = getListViewProperty('selectedOrgID');
    if (selectedOrgId &&
        getListViewProperty("itemType") === getEnumValue("itemType", "TITLE") &&
        window.catalogID > 0) {
        url += "&orgId=" + selectedOrgId;
    }
    openModal(url, "950px", "507px");
}

function openDrcRequestTextOnlyView(requestId) {
    var url = '/GetTreelineControl.aspx?controlName=/uc/galley/DrcCopyView.ascx&requestId=' + requestId;
    openModal(url, "600px", "500px");
}

function addToModalStack(elementId, callback) {
    if (_.isNil(window.modalStack)) {
        window.modalStack = [];
    }

    window.modalStack.push(elementId);
    $(elementId).on('close', callback);
}

function attachEscapeKeyEventListener() {
    $(document.body).off('keyup.escapeModal').on('keyup.escapeModal', function (e) {
        if (e.keyCode === 27) {
            var modal = window.modalStack.pop();
            $(modal).trigger('close');
        }
    });
}

function openNewsletterGenerator() {
    var url = '/GetTreelineControl.aspx?controlName=/uc/support/NewsletterGenerator.ascx';
    openModal(url, "500px", "600px");
}

// Payment Modal

function getPaymentModalId() {
    return 'payment-control';
}

function closePaymentModal() {
    var id = getPaymentModalId();
    closeMultiModal(id);
}

function openPaymentModal(config) {
    var defaults = {
        pluginId: 'payment-control-plugin',
        width: '500px',
        height: '600px',
        referenceIds: [],
        sku: null,
        promoCode: null,
        onClose: function () {
            closePaymentModal();
        },
        onSubmitDone: function (data) {
            console.log(data);
        },
        onSubmitFail: function (data) {
            console.log(data);
        }
    };
    var settings = $.extend({}, defaults, config);

    openMultiModal({
        id: getPaymentModalId(),
        html: '<div id="' + settings.pluginId + '" style="display:flex; flex-direction:column; height:100%"></div>',
        width: settings.width,
        height: settings.height,
        onLoad: function () {
            ePlus.modules.paymentControl.initialize({
                containerId: settings.pluginId,
                referenceIds: settings.referenceIds,
                sku: settings.sku,
                promoCode: settings.promoCode,
                onClose: settings.onClose,
                onSubmitDone: settings.onSubmitDone,
                onSubmitFail: settings.onSubmitFail
            });
        }
    });
}

function openMarkupShareTool(callback) {
    var url = '/GetTreelineControl.aspx?controlName=/uc/catalog/markupShareTool/MarkupShareTool.ascx';
    openModal(url, "600px", "490px", function () {
        if (typeof (callback) === 'function') {
            callback();
        }
    });
}

function addReviewCopiesToOrder() {
    var url = "GetTreelineControl.aspx?controlName=/uc/product/request/OrderReviewCopyRequests.ascx";
    openChildModal(url, "600px", "600px");
}

function createReviewCopyRequestForUser(sku) {
    var url = "GetTreelineControl.aspx?controlName=/uc/product/request/CreateReviewCopyRequestForUser.ascx";
    var selectedOrgId = getListViewProperty('selectedOrgID');

    if (sku) {
        url += "&sku=" + sku;
    }
    if (selectedOrgId &&
        getListViewProperty("itemType") === getEnumValue("itemType", "TITLE") &&
        window.catalogID > 0) {
        url += "&orgId=" + selectedOrgId;
    }

    openModal(url, "600px", "580px");
}

function openClientBillingProfile(clientid, orgid) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/support/clientBillingProfile.ascx&clientID=" + clientid + "&orgID=" + orgid;
    openMultiModal({
        id: 'billingprofile',
        url: url,
        width: "600px",
        height: "500px"
    });
}

function openLibraryData(fscskey) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/organization/libraryData.ascx&modal=1&fscskey=" + fscskey;
    openMultiModal({
        id: 'librarydata',
        url: url,
        width: "550px",
        height: "360px"
    });
}

function openTransferUsers(orgId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/support/transferUsers.ascx&orgId=" + orgId;
    openMultiModal({
        id: 'transfer-users',
        url: url,
        width: "600px",
        height: "450px"
    });
}

function openUserAdvocacy(appUserId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/support/advocacy/UserAdvocacy.ascx&appUserId=" + appUserId;
    openMultiModal({
        id: 'user-advocacy',
        url: url,
        width: "600px",
        height: "450px"
    });
}

function openMessageAffiliationMembers(affiliationId) {
    var url = '/GetTreelineControl.aspx?controlName=/uc/affiliations/MessageAffiliationMembers.ascx&affiliationId=' + affiliationId;

    openDialog({
        id: 'message-affiliation-members',
        url: url,
        isModal: true,
        isDraggable: true,
        width: '500px'
    });
}

function openManageLegacyAffiliation(affiliationId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/affiliations/ManageLegacyAffiliation.ascx";
    if (affiliationId) {
        url += "&affiliationId=" + affiliationId;
    }

    var onClose = function () {
        var doShowUnsavedChangesWarning = window.ePlus.modules.manageLegacyAffiliation.doShowUnsavedChangesWarning();
        if (doShowUnsavedChangesWarning && !confirm(getRes("you_have_unsaved_changes_will_be_lost"))) {
            return false;
        }

        if (affiliationId > 0) {
            var doShowUnsavedSubscriptionChangesWarning = window.ePlus.modules.manageAffiliationEmail.hasUnsavedChanges();
            if (doShowUnsavedSubscriptionChangesWarning && !confirm(getRes('you_have_unsaved_subscription_changes'))) {
                return false;
            }
        }

        return true;
    }

    openModal(url, "700px", "475px", null, onClose);
}


function openManageUserAffiliation(affiliationId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/affiliations/ManageUserAffiliation.ascx";
    if (affiliationId) {
        url += "&affiliationId=" + affiliationId;
    }

    var onClose = function () {
        var doShowUnsavedChangesWarning = window.ePlus.modules.manageUserAffiliation.doShowUnsavedChangesWarning();
        if (doShowUnsavedChangesWarning && !confirm(getRes("you_have_unsaved_changes_will_be_lost"))) {
            return false;
        }

        if (affiliationId > 0) {
            var doShowUnsavedSubscriptionChangesWarning = window.ePlus.modules.manageAffiliationEmail.hasUnsavedChanges();
            if (doShowUnsavedSubscriptionChangesWarning && !confirm(getRes('you_have_unsaved_subscription_changes'))) {
                return false;
            }
        }

        return true;
    }

    openModal(url, "700px", "475px", null, onClose);
}

function openManageImprintGroup(imprintGroupId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/imprints/ManageImprintGroup.ascx";
    if (imprintGroupId) {
        url += "&imprintGroupId=" + imprintGroupId;
    }
    openModal(url, "400px", "300px");
}

function openScorecardModal(orgId, storeId) {
    var params = {
        orgId: orgId
    };
    if (storeId) {
        params.storeId = storeId;
    }

    var url = "/GetTreelineControl.aspx?controlName=/uc/analytics/ScorecardFrame.ascx&" + $.param(params);

    openDialog({
        id: "scorecardFrame",
        url: url,
        isDraggable: true,
        width: "820px",
        height: "610px",
        zIndex: 900110
    });
}

function openAffiliationReviewsModal(affiliationId, createdDateMaxDaysBack) {
    var params = {
        affiliationId: affiliationId,
        createdDateMaxDaysBack: createdDateMaxDaysBack,
        page: ePlus.util.getEnumValue('communityPage', 'REVIEWS')
    };

    var url = "/GetTreelineControl.aspx?controlName=/uc/affiliations/AffiliationAppFrame.ascx&"
        + $.param(params);

    openMultiModal({
        id: "affiliationReviewsFrame",
        url: url,
        contentClass: "community-review-iframe",
        isFixup: true
    });

    window.removeEventListener("message", loadTitleModalEventCallback);
    window.addEventListener("message", loadTitleModalEventCallback);
}

function loadTitleModalEventCallback(evt) {
    try {
        var jsonData = JSON.parse(evt.data);
        if (jsonData.hasOwnProperty('action') && jsonData.action === 'openTitleModal'
            && jsonData.hasOwnProperty('sku')) {
            var options = {
                sku: jsonData.sku
            };
            loadMultiModalTitle(options);
        }
    } catch (e) { };
}

function openAffiliationCatalogsModal(affiliationId, createdDateMaxDaysBack) {
    var params = {
        affiliationId: affiliationId,
        createdDateMaxDaysBack: createdDateMaxDaysBack,
        page: ePlus.util.getEnumValue('communityPage', 'CATALOGS')
    };

    var url = "/GetTreelineControl.aspx?controlName=/uc/affiliations/AffiliationAppFrame.ascx&"
        + $.param(params);

    openMultiModal({
        id: "affiliationCatalogsFrame",
        url: url,
        contentClass: "community-catalog-iframe",
        isFixup: true
    });

    window.removeEventListener("message", loadCatalogEventCallback);
    window.addEventListener("message", loadCatalogEventCallback);
}

function loadCatalogEventCallback(evt) {
    try {
        var jsonData = JSON.parse(evt.data);
        if (jsonData.hasOwnProperty('action') && jsonData.action === 'navigateToCollection'
            && jsonData.hasOwnProperty('catalogId')) {

            closeMultiModal('affiliationCatalogsFrame');
            goToCatalog(jsonData.catalogId, 0);
        }
    } catch (e) { };
}

function openMultiModal(options) {
    // Trigger a page interaction event
    $(window).trigger('interaction', [function () {
        WebuiPopovers.hideAll();

        createMultiModalDialog(options);
        showMultiModalLoadingAnimation(options.id, getRes('loading'));

        var onLoad = function () {
            hideMultiModalLoadingAnimation(options.id);

            if (typeof options.onLoad === 'function') {
                var elem = $('#pop-modal-content-' + options.id)[0];
                options.onLoad(elem);
            }
        };

        if (options.url) {
            $('#pop-modal-inner-' + options.id).load(options.url, options.data, function () {
                onLoad();
            });
        } else {
            onLoad();
        }
    }]);
}

function createMultiModalDialog(options) {
    if ($('#pop-modal-' + options.id).length) {
        closeMultiModal(options.id);
    }

    var zIndex = options.zIndex ? options.zIndex : calculateMultiModalZIndex();
    var contentClasses = ['modal-content']
    var innerClasses = [options.isFixup ? 'modal-inner-fixup' : 'modal-inner'];

    contentClasses.push('modal-content-' + (options.isFixup ? 'fixup' : 'normal'));

    if (options.contentClass) contentClasses.push(options.contentClass);
    if (options.innerClass) innerClasses.push(options.innerClass);

    var closeButtonClass = options.closeButtonClass || 'defaultModalCloseButton';
    var html = '<div id="pop-modal-' + options.id + '" data-page-elem="' + options.id + '" class="modal-frame" style="z-index:' + zIndex + ';"></div>'
        + '<div id="pop-modal-content-' + options.id + '" class="' + contentClasses.join(' ') + '" style="z-index:' + (zIndex + 1) + '; width:' + options.width + ';height:' + options.height + ';">'
        + '<div id="pop-modal-inner-' + options.id + '" class="' + innerClasses.join(' ') + '"></div>'
        + '<div id="m-close-' + options.id + '" title="' + getRes("close") + '" class="' + closeButtonClass + ' icon-close-icon iconSVG_Darker icon-btn-responsive"></div>'
        + '</div>';

    $('#form1').append(html);

    var MULTI_MODAL_OPACITY = 0.7;
    var MULTI_MODAL_OPACITY_INCREMENT = 0.1;

    $('.modal-frame').each(function (i) {
        $(this).css('opacity', MULTI_MODAL_OPACITY - (i * MULTI_MODAL_OPACITY_INCREMENT));
    });

    if (options.html) {
        $('#pop-modal-inner-' + options.id).append(options.html);
    }

    $('html, body').css('overflow', 'hidden');

    $('#pop-modal-' + options.id).on('click', function () {
        closeMultiModal(options);
    });

    $('#m-close-' + options.id).on('click', function () {
        closeMultiModal(options);
    });

    attachMultiEscapeKeyEventListener(options.onClose);
}

function calculateMultiModalZIndex() {
    var MULTI_MODAL_ZINDEX_OFFSET = 2;
    var MULTI_MODAL_START_ZINDEX = 900005
    var zIndex = MULTI_MODAL_START_ZINDEX;
    $('.modal-frame, .modalContent, .modalContentFrame').each(function () {
        zIndex = Math.max(zIndex, $(this).zIndex() + MULTI_MODAL_ZINDEX_OFFSET);
    });
    return zIndex;
}

function getOpenModalsCount() {
    return $('.modal-frame').length;
}

function closeMultiModal(arg) {
    var options = typeof arg === 'string'
        ? { id: arg }
        : arg || {};

    $('#m-close-' + options.id).webuiPopover('hide');

    var doClose = true;

    if (typeof options.onClose === 'function') {
        doClose = options.onClose();
    }

    if (doClose) {
        WebuiPopovers.hideAll();

        // Only reset the page overflow if this is the only modal on the page
        if (getOpenModalsCount() === 1) {
            $('html, body').css('overflow', 'auto');
        }

        closeMultiModalAction(options.id);
    }
}

function closeMultiModalAction(id) {
    $('.help').webuiPopover('hide'); // Hide any active help popups

    if ($('#pop-modal-' + id).length) {
        $('.popModalButton').off('click');
        $('#pop-modal-' + id).remove();
        $('#pop-modal-content-' + id).remove();
    }
}

function attachMultiEscapeKeyEventListener(onClose) {
    $(document.body).off('keyup.escapeModal').on('keyup.escapeModal', function (e) {
        if (e.keyCode !== 27) return;

        closeMultiModal({
            id: $('.modal-frame').last().data('page-elem'),
            onClose: onClose
        });
    });
}

function showMultiModalLoadingAnimation(id, iHtml) {
    var dHtml = '<div id="saving-modal-' + id + '" class="blockDivSaving saveBody">';
    dHtml += '<div class="saveContainer">';
    dHtml += '<div class="saveContent">';
    dHtml += '<div class="column" id="progress-animation"></div>';
    dHtml += '<div class="column-spaced">' + iHtml + '</div>';
    dHtml += '</div>';
    dHtml += '</div>';
    dHtml += '</div>';
    $('#pop-modal-inner-' + id).after(dHtml);
    $('#progress-animation').html(templateCache.loadingAnimation({ svgLoaderClass: '' }));
}

function hideMultiModalLoadingAnimation(id) {
    $('#saving-modal-' + id).remove();
}

function openCrmPage(entityType, entityId, entityPage) {
    var url = '/GetTreelineControl.aspx?controlName=/uc/E360/E360IFrame.ascx&entityType=' + entityType + '&entityId=' + entityId;

    if (entityPage !== 'undefined') {
        url += '&entityPage=' + entityPage;
    }

    window.removeEventListener('message', window.ePlus.modules.e360.messageHandler);
    window.addEventListener('message', window.ePlus.modules.e360.messageHandler);

    openModal(url, '750px', '600px', null, null, null, null, 'defaultModalCloseButton crm-modal-close-button');
}

function openCommunityWizard(isAutoOpen) {
    var url = '/GetTreelineControl.aspx?controlName=/uc/affiliations/AffiliationWizardFrame.ascx&isAutoOpen=' + isAutoOpen;

    openDialog({
        id: 'community-wizard-modal',
        isDraggable: false,
        url: url,
        width: '725px',
        height: '650px',
        isModal: true
    });

    window.removeEventListener("message", window.ePlus.modules.communityWizard.messageHandler);
    window.addEventListener("message", window.ePlus.modules.communityWizard.messageHandler);
}

function openSeriesData(sku) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/SeriesData.ascx&sku=" + sku;
    openMultiModal({
        id: 'series-data',
        url: url,
        width: "830px",
        height: "490px",
        contentClass: "pad-10"
    });
}

function closeSeriesData() {
    closeMultiModal('series-data');
}

function openSendNotification(appUserId) {
    var url = '/GetTreelineControl.aspx?controlName=/uc/notices/CreateNotice.ascx&receivingAppUserId=' + appUserId;
    openMultiModal({
        id: 'send-notice-modal',
        url: url,
        width: "300px",
        height: "300px",
        isFixup: true
    })
}

function openNewsletterBannersAdmin() {
    var id = getPromoteModalId('0');
    var url = '/promotions/0/newsletters/banners';
    openMultiModal({
        id: id,
        url: url,
        width: '700px',
        height: '520px',
        isFixup: true
    });
}

function openCommunicationSettings() {
    WebuiPopovers.hideAll();
    EdelweissComponentsLoader.handleRender(function () {
        EdelweissComponents.Newsletter.NewsletterCommunicationsDrawer.render(document.getElementById("newsletterCommunicationsRoot"));
    });
}
;
// E+ Dialog Control
// TODO: Convert to jQuery UI widget
// TODO: Add support for multiple dialogs to be open at once
// TODO: Add automatic z-index calculation when multiple open dialogs supported
// TODO: Add option to force dialog to maintain position when window scrolled

function closeDialog(dialog) {
    $(dialog).trigger("close");
}

function closeDialogs() {
    $(".ePlusDialog").each(function () {
        closeDialog(this);
    });
}

function getDialogDefaults() {
    return {
        id: null,
        title: null,
        url: null,
        content: null,
        buttons: null,
        width: "auto",
        height: "auto",
        zIndex: "auto",
        minWidth: 400,
        buttonSpace: 5,
        data: null,
        doAutoOpen: true,
        doCloseOnOutsideClick: false,
        isModal: false,
        blockBackgroundColor: "rgba(0, 0, 0, .5)",
        modalBlockBackgroundColor: "rgba(0, 0, 0, .5)",
        isDraggable: true,
        draggableOptions: {
            containment: "window",
            scroll: false
        },
        onInit: null,
        onBeforeShow: null,
        onShow: null,
        onBeforeHide: null,
        onHide: null,
        onBeforeClose: null,
        onClose: null,
        classes: {
            dialog: null,
            dialogForm: null,
            dialogHeader: null,
            dialogContent: null,
            dialogFooter: null,
            dialogButtons: null,
            dialogButtonsIcon: null,
            dialogButtonsText: null,
            dialogCloseButton: null,
            dialogCloseButtonIcon: null
        }
    };
}

function openDialog(options) {
    var dialog,
        form,
        content,
        block,
        modalBlock,
        defaults = getDialogDefaults();
    
    options = $.extend({}, defaults, options);

    if (doesDialogExists()) {
        return;
    }
    // Currently only supporting one open modeless dialog at a time
    closeDialogs();

    function resizeDialog() {
        if (options.width === "auto") {
            var width = form.outerWidth(true);

            if (width < options.minWidth) {
                width = options.minWidth;
            }

            dialog.css("width", width);
        }

        if (options.height === "auto") {
            var height = form.outerHeight(true);
            dialog.css("height", height);
        }
    }

    function centerDialog() {
        var win = $(window),
            left = win.width() / 2 - dialog.width() / 2,
            top = win.height() / 2 - dialog.height() / 2;

        dialog.css({
            left: Math.ceil(left),
            top: Math.ceil(top)
        });
    }

    function showDialog() {
        if (typeof options.onBeforeShow === "function") {
            options.onBeforeShow(dialog);
        }

        if (!dialog.data("isPositioned")) {
            resizeDialog();
            centerDialog();

            dialog.data("isPositioned", true);
        }

        dialog.css("visibility", "visible");
        
        if (typeof options.onShow === "function") {
            options.onShow(dialog, form);
        }

        //Modal positioning fix for iOS
        if (isIOs()) {
            $(dialog)
                .css({
                    position: 'fixed',
                    top: ($(window).height() / 2 - dialog.height() / 2) + 'px',
                    bottom: 'auto'
                });
        }
    }

    function hideDialog() {
        if (typeof options.onBeforeHide === "function") {
            options.onBeforeHide(dialog);
        }

        dialog.css("visibility", "hidden");

        if (typeof options.onHide === "function") {
            options.onHide(dialog);
        }
    }

    function destroyDialog() {
        if (modalBlock) {
            $('#' + options.id).unwrap();
        }
        $(document.body).off("keyup.dialog");
        dialog.remove();
    }

    function closeDialog() {
        if (typeof options.onBeforeClose === "function") {
            options.onBeforeClose(dialog);
        }

        destroyDialog();

        if (typeof options.onClose === "function") {
            options.onClose(dialog);
        }
    }

    function enableBlock(message) {
        block =
            $("<div></div>", {
                "id": options.id + "Block",
                "class": "ePlusDialogBlock"
            }).zIndex(dialog.zIndex() + 2);

        if (options.blockBackgroundColor) {
            block.css("background-color", options.blockBackgroundColor);
        }

        var blockInner = $("<div></div>", {
            "class": "ePlusDialogBlockInner",
            "text": message
        });

        block.append(blockInner).appendTo(dialog);
    }

    function disableBlock() {
        block.remove();
    }

    function enableModalBlock() {
        modalBlock =
            $("<div></div>", {
                "id": options.id + "ModalBlock",
                "class": "ePlusDialogModalBlock"
            }).zIndex(dialog.zIndex() - 1);

        if (options.modalBlockBackgroundColor) {
            modalBlock.css("background-color", options.modalBlockBackgroundColor);
        }

        $('#' + options.id).wrap(modalBlock);
    }

    function disableModalBlock() {
        modalBlock.remove();
    }

    function initializeDialog() {
        if (options.isModal) {
            enableModalBlock();
        }

        if (options.isDraggable) {
            dialog.draggable(options.draggableOptions);
        }

        if (options.doAutoOpen) {
            showDialog();
        }

        if (options.data) {
            dialog.data(options.data);
        }

        if (options.doCloseOnOutsideClick) {
            var event = "mouseup." + options.id;

            $(document.body).off(event).on(event, function (e) {
                if (!$(e.target).closest("#" + options.id).length) {
                    closeDialog();
                }
            });
        }

        if (!_.isNil(window.modalStack)) {
            window.modalStack.push("#" + options.id);
        }

        attachEscapeKeyEventListener();

        form.on({
            "submit": function (event) {
                if (typeof options.onSubmit === "function") {
                    options.onSubmit(event, dialog, form);
                }

                return false;
            }
        });

        dialog.data("isInitialized", true);

        if (typeof options.onInit === "function") {
            options.onInit(dialog, form);
        }
    }

    function buildDialogCloseButton() {
        var closeButtonIcon = $("<span></span>", {
            "class": ["ePlusDialogCloseButtonIcon", "icon-close-icon", "iconSVG_Darker", options.classes.dialogCloseButtonIcon].join(" ")
        }),
            closeButton = $("<button></button>", {
                "type": "button",
                "class": ["ePlusDialogCloseButton", options.classes.dialogCloseButton].join(" "),
                "title": getRes("close")
            }).zIndex(dialog.zIndex() + 1).on("click", function () {
                closeDialog();
            });

        dialog.append(closeButton.append(closeButtonIcon));
    }

    function buildDialogHeader() {
        var title = $("<h1></h1>", {
            "class": ["ePlusDialogHeader", options.classes.dialogHeader].join(" ")
        }).html(options.title);

        form.append(title);
    }

    function buildDialogContent() {
        content = $("<div></div>", {
            class: ["ePlusDialogContent", options.classes.dialogContent].join(" ")
        });

        form.append(content);
    }

    function loadDialogContent(callback) {
        function _callback() {
            if (typeof callback === "function") {
                callback(content);
            }
        }

        if (options.url) {
            content.load(options.url, function () {
                removeDialogDimensionsIfDefined();
                removeLoadingMessage();
                _callback();
            });
        } else {
            if (options.content) {
                content.html(options.content);
            }

            removeLoadingMessage();
            _callback();
        }
    }

    function buildDialogButtons() {
        var results = [];

        $.each(options.buttons, function (i, config) {
            var button = $("<button></button>", {
                "type": config.type || "button",
                "class": ["ePlusDialogButton", options.classes.dialogButtons].join(" "),
                "title": config.tooltip
            }),
                buttonIconSpan,
                buttonTextSpan;

            // If a button icon is specified, add it to the button
            if (config.icon) {
                buttonIconSpan = $("<span></span>", {
                    "class": ["ePlusDialogButtonIconSpan", "iconSVG", options.classes.dialogButtonsIcon, config.icon].join(" ")
                });

                button
                    .addClass('ePlusDialogButtonWithIcon')
                    .append(buttonIconSpan);
            }

            // If button text is specified, add it to the button
            if (config.text) {
                buttonTextSpan = $("<span></span>", {
                    "class": ["ePlusDialogButtonTextSpan", options.classes.dialogButtonsText].join(" "),
                    "text": config.text
                });

                button
                    .addClass('ePlusDialogButtonWithText')
                    .append(buttonTextSpan);
            }

            // If a button icon and text are specified, add some space between them
            if (buttonIconSpan && buttonTextSpan) {
                buttonTextSpan.css("margin-left", options.buttonSpace);
            }

            if (typeof config.onClick === "function") {
                button.on("click", function (event) {
                    config.onClick(event, dialog, form);
                });

            }

            results.push(button[0]);
        });

        return results;
    }

    function buildDialogFooter() {
        var buttons = buildDialogButtons(),
            footer = $("<p></p>", {
                "class": ["ePlusDialogFooter", options.classes.dialogFooter].join(" ")
            }).append(buttons);

        form.append(footer);
    }

    function buildDialogForm(callback) {
        buildDialogCloseButton();

        form = $("<form></form>", {
            "id": options.id + "Form",
            "class": ["ePlusDialogForm", options.classes.dialogForm].join(" ")
        });

        dialog.append(form);

        function _callback() {
            if (typeof callback === "function") {
                callback(form);
            }
        }

        if (options.title) {
            buildDialogHeader();
        }

        buildDialogContent();
        showLoadingMessage();

        if (options.buttons) {
            buildDialogFooter();
        }
        
        loadDialogContent(_callback);
    }

    function buildDialog() {
        options.id = options.id || "dialog";

        dialog =
            $("<div></div>", {
                "id": options.id,
                "class": ["ePlusDialog", options.classes.dialog].join(" "),
                "width": options.width !== "auto" ? options.width : 0,
                "height": options.height !== "auto" ? options.height : 0
            })
                .zIndex(options.zIndex !== "auto" ? options.zIndex : 99999999)
                .data({
                    isInitialized: false,
                    isPositioned: false,
                    options: options
                })
                .appendTo(document.body)
                .on({
                    "hide": hideDialog,
                    "show": showDialog,
                    "resize": resizeDialog,
                    "center": centerDialog,
                    "close": closeDialog,
                    "destroy": destroyDialog,
                    "showBlock": function (event, message) {
                        enableBlock(message);
                    },
                    "hideBlock": disableBlock
                })
                .one({
                    "init": initializeDialog
                });

        buildDialogForm(function () {
            initializeDialog();
        });
    }

    // Trigger a page interaction event
    $(window).trigger('interaction', [function () {
        buildDialog();
    }]);

    function showLoadingMessage() {
        form.append('<div class="eplus-dialog-loading">' + templateCache.loadingAnimation({ svgLoaderClass: '' })
            + '<span class="eplus-dialog-loading-text">' + getRes('loading') + '</span>' + '</div>');
        setDialogDimensionsIfDefined()
        centerDialog();
    }

    function setDialogDimensionsIfDefined() {   
        options.width === "auto" ?
            dialog.css('width', 300) : dialog.css('width', options.width);

        options.height === "auto" ?
            dialog.css('height', 300) : dialog.css('height', options.height);
    }

    function removeDialogDimensionsIfDefined() {
        if (options.width === "auto") {
            dialog.css('width', 0);
        }

        if (options.height === "auto") {
            dialog.css('height', 0);
        }
    }

    function removeLoadingMessage() {
        $('.eplus-dialog-loading', form).remove();
    }

    function doesDialogExists() {
        return $("#" + options.id).length;
    }
};
// JScript File
function resizeThings(popTrigger) {
    var dom = ePlus.util.dom;
    var leftNavElem = document.getElementById("leftNav");
    if (dom.existsByElement(leftNavElem)) {
        var isLeftNavContracted = dom.hasClassByElement(leftNavElem, "leftNavContracted");
        if (window.innerWidth < 1100) {
            if (!isLeftNavContracted) {
                dom.hideById("leftNavLock");
                if (typeof ePlus.modules.leftNav.fixLeftNav === "function") {
                    ePlus.modules.leftNav.fixLeftNav();
                }
                document.body.style.marginLeft = "0";
                $('.app-header').css('width', '100%');
            }
        } else {
            dom.showById("leftNavLock");
            if (!isLeftNavContracted && getListViewProperty("fixedNavPref") !== "fixed") {
                if (typeof ePlus.modules.leftNav.unFixLeftNav === "function") {
                    ePlus.modules.leftNav.unFixLeftNav();
                }
            } else if (!isLeftNavContracted) {
                if (typeof ePlus.modules.leftNav.fixLeftNav === "function") {
                    ePlus.modules.leftNav.fixLeftNav();
                }
            }
        }
    }
    if (dom.existsById("listViewHeaderFill")) {
        resizeListViewHeader();
    }
    if (dom.existsByClassName("markupComps")) {
        resizeDashboard();
        resizeMyEdelweiss();
        resizePublisherScroller();
    }
    if (dom.existsByClassName("DashboardDiv")) {
        resizeDashboard(popTrigger);
        resizeMyEdelweiss();
        resizePublisherScroller();
    }
}

function resizeListViewHeader() {
    if ($("#lvh_Div").length > 0) {
        var controlWidth = $("#lvh_Div").width();
        $("#listViewHeaderFill").css("width", controlWidth - 110 + "px");
    }
    resizeMyEdelweiss();
}


function resizeTitleContent() {
    var controlWidth = $(".standardTitleFrame").width() * 1;

    if ($(".dashboardFrame").length > 0) {
        $(".dashboardFrame").css("min-width", controlWidth - 240 - 85 + "px");
        $(".dashboardFrame").css("width", controlWidth - 260 + "px");
        $(".DashboardDiv").css("min-width", controlWidth - 240 + "px");
        $(".thumbScroll").css("min-width", controlWidth - 260 + "px");
    }
}

function resizeReviewContent() {
    var tContentWidth = $("#listContent").width();
    $(".rContent").css("width", ((tContentWidth - 36) / 2) + "px");
    $(".rCenter").css("width", (((tContentWidth - 36) / 2) - 165) + "px");
}

function sizeTitleContent(sku) {
    //Get the width of the main div in the TitleFrame_Basic page
    var controlWidth = $("#titleFrame_Basic" + sku).width() * 1;

    //Set the width of the main section to the right of the title
    $("#lcd" + sku).css("width", controlWidth - 160 + "px");

    //Set the width of the title to account for long titles
    var rightSection = $("#tr_Check" + sku).width();
    $("#title_" + sku).css("width", controlWidth - 200 - rightSection + "px")

    //Adjust if KeyNote is rendered
    if ($("#kn" + sku).length > 0) {
        if (controlWidth - 190 < 600) {
            $("#bib" + sku).css("width", "480px");
            $("#kn" + sku).css("width", "480px");
        } else {
            $("#bib" + sku).css("width", "50%");
            $("#kn" + sku).css("width", "45%");
        }
    }

    //Adjust if rep markup section is present
    if ($("#markupTitleFrame" + sku).length > 0) {
        $("#markupTitleFrame" + sku).css("width", controlWidth - 550 + "px")
        $("#bib" + sku).css("max-width", "370px")
        $("#mTagBox" + sku).css("width", controlWidth - 650 + "px")
        $("#markupNoteContainer_" + sku).css("width", controlWidth - 670 + "px")
    }
}

function resizeDashboard(popTrigger) {
    var controlWidth = $(".mainContentFrame").width() * 1;
    if ($("#welcomeDiv").length > 0) {
        controlWidth = $("#welcomeActions").width();
        popTrigger = undefined;
    }
    
    ///added 1 extra pixel (252 instead of 251 because of issues German browsers were having
    $(".dashRightSide").css("width", controlWidth - 252 + "px");
    if ($(".analyticDash").length > 0) {
        controlWidth = controlWidth - 200
        $(".analyticDashDetail").css("width", controlWidth - 5 + "px");
    }

    if (popTrigger == undefined) {
        $(".dashboardFrameNew").each(function (i, obj) {
        var dashType = $(this).attr("data-dashType");
        var controlWidth = $(".parent_" + dashType).width() * 1;
        $(".dashboardFrame" + dashType).css("width", controlWidth - 80 + "px");
        var slidesToScroll = (controlWidth - 80) / 140;
        if ($(this).attr("data-items") < slidesToScroll) {
            slidesToScroll = $(this).attr("data-items");
        }

        $(".dashCenterPlease").css("margin-left", (controlWidth - 80) / 2 - 40 + "px")

        var doShowHidden = !!dashboardExclusionFilters[dashType];

        // Need to show all exclusions since we're reinitializing slick here
        toggleDashboardExclusionFilter(dashType, true);

        $('.dashboardFrame' + dashType).slick('unslick');
        $('.dashboardFrame' + dashType).slick({
            lazyLoad: 'ondemand',
            dots: false,
            infinite: false,
            speed: 300,
            slidesToShow: slidesToScroll + 2,
            centerMode: false,
            variableWidth: true,
            slidesToScroll: slidesToScroll - 1
        });

        // Re-apply exclusion filter if it was previously set
        toggleDashboardExclusionFilter(dashType, doShowHidden);        
    });
    }
}

function resizeMyEdelweiss() {
    var controlWidth = "";
    if ($("#listViewHeaderFill").length > 0) {
        controlWidth = +$("#listViewHeaderFill").width();
        var myLabelWidth = +$(".myLabel").width();
        $(".myEdelRight").css("width", (controlWidth - 51 - myLabelWidth) + "px");
    } else {
        controlWidth = +$(".myEdelDash").width();
        $(".myEdelRight").css("width", controlWidth - 201 + "px");
    }
}

function resizePublisherScroller() {
    //var controlHeight = $("#sortableDash").height() * 1;
    var controlHeight = $(".dashRightSide").height() * 1;
    var myPublisherHeight = 0
    if ($("#myPublisherDiv").length > 0) {
        controlHeight -= $("#myPublisherDiv").height()
    }
    if (controlHeight < 600) {
        controlHeight = 600
    }
    $("#publisherList").css("height", controlHeight - 84 + "px");
    $("#publisherScroller").css("height", controlHeight - 83 + "px");
    $("#publisherDiv").css("height", controlHeight - 23 + "px");
    $("#pubFiller").css("height", controlHeight - 153 + "px");
}
;
// edel.printing.js
// Legacy Edelweiss PDF Template Functions

function validatePrintSections(sectionId) {
    var sectionIds = getPrintSectionsToUncheck(sectionId);

    if (sectionIds.length) {
        var selectors = $.map(sectionIds, function (sectionId) {
            return ".printSection[data-sectionid=" + sectionId + "]";
        }).join(",");

        $(selectors)
            .removeClass("box_checked")
            .addClass("box_unchecked");
    }
}

function savePrintPreferences(type, name, sections) {
    $.getJSON("/GetJSONData.aspx?builder=SavePrintPreferences", {
        type: type,
        name: name,
        sections: sections
    });
}

function savePrintPreferencesByType(type, name, data) {
    $.getJSON("/GetJSONData.aspx?builder=SavePrintPreferencesByType", {
        type: type,
        name: name,
        data: data
    });
}

function savePrintPreferencesCharCount(type, name, data) {
    console.log(type, name, data);

    $.getJSON("/GetJSONData.aspx?builder=SavePrintPreferencesCharCount", {
        type: type,
        name: name,
        data: data
    });
}

function savePrintSectionsPreference(templateName) {
    var data = $(".printSection.box_checked").map(function () {
        return $(this).data("sectionid");
    }).get().join(",");
        
    savePrintPreferences("PrintPreferences", templateName, data);
}

function savePrintHiResImagesPreference(templateName) {
    var data = [];

    data.push({
        name: "image",
        value: $("#highResolutionImages").hasClass("box_checked") ? "enabled" : ""
    });

    savePrintPreferencesByType("PrintPreferencesImages", templateName, JSON.stringify(data));
}

function savePrintBorderPreference(templateName) {
    var data = [];

    data.push({
        name: "border",
        value: $("#border").hasClass("box_checked") ? "enabled" : ""
    });

    savePrintPreferencesByType("PrintPreferencesBorder", templateName, JSON.stringify(data));
}

function savePrintMarginPreference(templateName, value) {
    var data = [];

    data.push({
        name: "margin",
        value: "" + (value || "")
    });

    savePrintPreferencesByType("PrintPreferencesMargin", templateName, JSON.stringify(data));
}

function savePrintCharCountPreferences(templateName) {
    var data = [];

    $(".advancedPrintOption-charCount").each(function () {
        validateCharCount(this);

        data.push({
            id: this.id,
            count: $(this).val().toString()
        });
    });

    savePrintPreferencesCharCount("PrintPreferencesCharCount", templateName, JSON.stringify(data));
}

function getDefaultCharCount(elem) {
    var isJacketCoverSelected = $(".printSection.jacketCover").hasClass("box_checked");
    return $(elem).data(isJacketCoverSelected ? "jacket" : "nojacket");
}

function validateCharCount(elem) {
    var $elem = $(elem),
        value = $elem.val();

    // If the char count entered isn't a positive integer, revert to the default char count
    if (!/^\d+$/.test(value)) {
        var charCount = getDefaultCharCount(elem);
        $elem.val(charCount).select();
        $elem.data("pref", null);
    } else {
        $elem.data("pref", value);
    }
}

function validateCharCounts(elem) {
    $(".advancedPrintOption-charCount").each(function () {
        var $self = $(this),
            prefValue = $self.data("pref");

        prefValue = prefValue == null ? "" : prefValue;

        if (!prefValue.length) {
            var charCount = getDefaultCharCount(this);
            $self.val(charCount);
        }
    });
};
// jquery.dynatree.js build 0.5.3
// Revision: 313, date: 2010-03-15 15:55:09
// Copyright (c) 2008-09  Martin Wendt (http://dynatree.googlecode.com/)
// Licensed under the MIT License.

var _canLog=true;function _log(mode,msg){if(!_canLog)
return;var args=Array.prototype.slice.apply(arguments,[1]);var dt=new Date();var tag=dt.getHours()+":"+dt.getMinutes()+":"+dt.getSeconds()+"."+dt.getMilliseconds();args[0]=tag+" - "+args[0];try{switch(mode){case"info":window.console.info.apply(window.console,args);break;case"warn":window.console.warn.apply(window.console,args);break;default:window.console.log.apply(window.console,args);}}catch(e){if(!window.console)
_canLog=false;}}
function logMsg(msg){Array.prototype.unshift.apply(arguments,["debug"]);_log.apply(this,arguments);}
var getDynaTreePersistData=undefined;var DTNodeStatus_Error=-1;var DTNodeStatus_Loading=1;var DTNodeStatus_Ok=0;;(function($){var Class={create:function(){return function(){this.initialize.apply(this,arguments);}}}
var DynaTreeNode=Class.create();DynaTreeNode.prototype={initialize:function(parent,tree,data){this.parent=parent;this.tree=tree;if(typeof data=="string")
data={title:data};if(data.key==undefined)
data.key="_"+tree._nodeCount++;this.data=$.extend({},$.ui.dynatree.nodedatadefaults,data);this.div=null;this.span=null;this.childList=null;this.isLoading=false;this.hasSubSel=false;},toString:function(){return"dtnode<"+this.data.key+">: '"+this.data.title+"'";},toDict:function(recursive,callback){var dict=$.extend({},this.data);dict.activate=(this.tree.activeNode===this);dict.focus=(this.tree.focusNode===this);dict.expand=this.bExpanded;dict.select=this.bSelected;if(callback)
callback(dict);if(recursive&&this.childList){dict.children=[];for(var i=0;i<this.childList.length;i++)
dict.children.push(this.childList[i].toDict(true,callback));}else{delete dict.children;}
return dict;},_getInnerHtml:function(){var opts=this.tree.options;var cache=this.tree.cache;var rootParent=opts.rootVisible?null:this.tree.tnRoot;var bHideFirstExpander=(opts.rootVisible&&opts.minExpandLevel>0)||opts.minExpandLevel>1;var bHideFirstConnector=opts.rootVisible||opts.minExpandLevel>0;var res="";var p=this.parent;while(p){if(bHideFirstConnector&&p==rootParent)
break;res=(p.isLastSibling()?cache.tagEmpty:cache.tagVline)+res;p=p.parent;}
if(bHideFirstExpander&&this.parent==rootParent){}else if(this.childList||this.data.isLazy){res+=cache.tagExpander;}else{res+=cache.tagConnector;}
if(opts.checkbox&&this.data.hideCheckbox!=true&&!this.data.isStatusNode){res+=cache.tagCheckbox;}
if(this.data.icon){res+="<img src='"+opts.imagePath+this.data.icon+"' alt='' />";}else if(this.data.icon==false){}else{res+=cache.tagNodeIcon;}
var tooltip=(this.data&&typeof this.data.tooltip=="string")?" title='"+this.data.tooltip+"'":"";res+="<a href='#' class='"+opts.classNames.title+"'"+tooltip+">"+this.data.title+"</a>";return res;},_fixOrder:function(){var cl=this.childList;if(!cl)
return;var childDiv=this.div.firstChild.nextSibling;for(var i=0;i<cl.length-1;i++){var childNode1=cl[i];var childNode2=childDiv.firstChild.dtnode;if(childNode1!==childNode2){this.tree.logDebug("_fixOrder: mismatch at index "+i+": "+childNode1+" != "+childNode2);this.div.insertBefore(childNode1.div,childNode2.div);}else{childDiv=childDiv.nextSibling;}}},render:function(bDeep,bHidden){var opts=this.tree.options;var cn=opts.classNames;var isLastSib=this.isLastSibling();if(!this.div){this.span=document.createElement("span");this.span.dtnode=this;if(this.data.key)
this.span.id=this.tree.options.idPrefix+this.data.key;this.div=document.createElement("div");this.div.appendChild(this.span);if(this.parent){this.parent.div.appendChild(this.div);}
if(this.parent==null&&!this.tree.options.rootVisible)
this.span.style.display="none";}
this.span.innerHTML=this._getInnerHtml();this.div.style.display=(this.parent==null||this.parent.bExpanded?"":"none");var cnList=[];cnList.push((this.data.isFolder)?cn.folder:cn.document);if(this.bExpanded)
cnList.push(cn.expanded);if(this.childList!=null)
cnList.push(cn.hasChildren);if(this.data.isLazy&&this.childList==null)
cnList.push(cn.lazy);if(isLastSib)
cnList.push(cn.lastsib);if(this.bSelected)
cnList.push(cn.selected);if(this.hasSubSel)
cnList.push(cn.partsel);if(this.tree.activeNode===this)
cnList.push(cn.active);if(this.data.addClass)
cnList.push(this.data.addClass);cnList.push(cn.combinedExpanderPrefix
+(this.bExpanded?"e":"c")
+(this.data.isLazy&&this.childList==null?"d":"")
+(isLastSib?"l":""));cnList.push(cn.combinedIconPrefix
+(this.bExpanded?"e":"c")
+(this.data.isFolder?"f":""));this.span.className=cnList.join(" ");if(bDeep&&this.childList&&(bHidden||this.bExpanded)){for(var i=0;i<this.childList.length;i++){this.childList[i].render(bDeep,bHidden)}
this._fixOrder();}},hasChildren:function(){return this.childList!=null;},isLastSibling:function(){var p=this.parent;if(!p)return true;return p.childList[p.childList.length-1]===this;},prevSibling:function(){if(!this.parent)return null;var ac=this.parent.childList;for(var i=1;i<ac.length;i++)
if(ac[i]===this)
return ac[i-1];return null;},nextSibling:function(){if(!this.parent)return null;var ac=this.parent.childList;for(var i=0;i<ac.length-1;i++)
if(ac[i]===this)
return ac[i+1];return null;},_setStatusNode:function(data){var firstChild=(this.childList?this.childList[0]:null);if(!data){if(firstChild){this.div.removeChild(firstChild.div);if(this.childList.length==1)
this.childList=null;else
this.childList.shift();}}else if(firstChild){data.isStatusNode=true;firstChild.data=data;firstChild.render(false,false);}else{data.isStatusNode=true;firstChild=this.addChild(data);}},setLazyNodeStatus:function(lts,opts){var tooltip=(opts&&opts.tooltip)?opts.tooltip:null;var info=(opts&&opts.info)?" ("+opts.info+")":"";switch(lts){case DTNodeStatus_Ok:this._setStatusNode(null);this.isLoading=false;this.render(false,false);if(this.tree.options.autoFocus){if(this===this.tree.tnRoot&&!this.tree.options.rootVisible&&this.childList){this.childList[0].focus();}else{this.focus();}}
break;case DTNodeStatus_Loading:this.isLoading=true;this._setStatusNode({title:this.tree.options.strings.loading+info,tooltip:tooltip,addClass:this.tree.options.classNames.nodeWait});break;case DTNodeStatus_Error:this.isLoading=false;this._setStatusNode({title:this.tree.options.strings.loadError+info,tooltip:tooltip,addClass:this.tree.options.classNames.nodeError});break;default:throw"Bad LazyNodeStatus: '"+lts+"'.";}},_parentList:function(includeRoot,includeSelf){var l=[];var dtn=includeSelf?this:this.parent;while(dtn){if(includeRoot||dtn.parent)
l.unshift(dtn);dtn=dtn.parent;};return l;},getLevel:function(){var level=0;var dtn=this.parent;while(dtn){level++;dtn=dtn.parent;};return level;},_getTypeForOuterNodeEvent:function(event){var cns=this.tree.options.classNames;var target=event.target;if(target.className.indexOf(cns.folder)<0&&target.className.indexOf(cns.document)<0){return null}
var eventX=event.pageX-target.offsetLeft;var eventY=event.pageY-target.offsetTop;for(var i=0;i<target.childNodes.length;i++){var cn=target.childNodes[i];var x=cn.offsetLeft-target.offsetLeft;var y=cn.offsetTop-target.offsetTop;var nx=cn.clientWidth,ny=cn.clientHeight;if(eventX>=x&&eventX<=(x+nx)&&eventY>=y&&eventY<=(y+ny)){if(cn.className==cns.title)
return"title";else if(cn.className==cns.expander)
return"expander";else if(cn.className==cns.checkbox)
return"checkbox";else if(cn.className==cns.nodeIcon)
return"icon";}}
return"prefix";},getEventTargetType:function(event){var tcn=event&&event.target?event.target.className:"";var cns=this.tree.options.classNames;if(tcn==cns.title)
return"title";else if(tcn==cns.expander)
return"expander";else if(tcn==cns.checkbox)
return"checkbox";else if(tcn==cns.nodeIcon)
return"icon";else if(tcn==cns.empty||tcn==cns.vline||tcn==cns.connector)
return"prefix";else if(tcn.indexOf(cns.folder)>=0||tcn.indexOf(cns.document)>=0)
return this._getTypeForOuterNodeEvent(event);return null;},isVisible:function(){var parents=this._parentList(true,false);for(var i=0;i<parents.length;i++)
if(!parents[i].bExpanded)return false;return true;},makeVisible:function(){var parents=this._parentList(true,false);for(var i=0;i<parents.length;i++)
parents[i]._expand(true);},focus:function(){this.makeVisible();try{$(this.span).find(">a").focus();}catch(e){}},_activate:function(flag,fireEvents){this.tree.logDebug("dtnode._activate(%o, fireEvents=%o) - %o",flag,fireEvents,this);var opts=this.tree.options;if(this.data.isStatusNode)
return;if(fireEvents&&opts.onQueryActivate&&opts.onQueryActivate.call(this.span,flag,this)==false)
return;if(flag){if(this.tree.activeNode){if(this.tree.activeNode===this)
return;this.tree.activeNode.deactivate();}
if(opts.activeVisible)
this.makeVisible();this.tree.activeNode=this;if(opts.persist)
$.cookie(opts.cookieId+"-active",this.data.key,opts.cookie);this.tree.persistence.activeKey=this.data.key;$(this.span).addClass(opts.classNames.active);if(fireEvents&&opts.onActivate)
opts.onActivate.call(this.span,this);}else{if(this.tree.activeNode===this){var opts=this.tree.options;if(opts.onQueryActivate&&opts.onQueryActivate.call(this.span,false,this)==false)
return;$(this.span).removeClass(opts.classNames.active);if(opts.persist){$.cookie(opts.cookieId+"-active","",opts.cookie);}
this.tree.persistence.activeKey=null;this.tree.activeNode=null;if(fireEvents&&opts.onDeactivate)
opts.onDeactivate.call(this.span,this);}}},activate:function(){this._activate(true,true);},deactivate:function(){this._activate(false,true);},isActive:function(){return(this.tree.activeNode===this);},_userActivate:function(){var activate=true;var expand=false;if(this.data.isFolder){switch(this.tree.options.clickFolderMode){case 2:activate=false;expand=true;break;case 3:activate=expand=true;break;}}
if(this.parent==null&&this.tree.options.minExpandLevel>0){expand=false;}
if(expand){this.toggleExpand();this.focus();}
if(activate){this.activate();}},_setSubSel:function(hasSubSel){if(hasSubSel){this.hasSubSel=true;$(this.span).addClass(this.tree.options.classNames.partsel);}else{this.hasSubSel=false;$(this.span).removeClass(this.tree.options.classNames.partsel);}},_fixSelectionState:function(){if(this.bSelected){this.visit(function(dtnode){dtnode.parent._setSubSel(true);dtnode._select(true,false,false);});var p=this.parent;while(p){p._setSubSel(true);var allChildsSelected=true;for(var i=0;i<p.childList.length;i++){var n=p.childList[i];if(!n.bSelected&&!n.data.isStatusNode){allChildsSelected=false;break;}}
if(allChildsSelected)
p._select(true,false,false);p=p.parent;}}else{this._setSubSel(false);this.visit(function(dtnode){dtnode._setSubSel(false);dtnode._select(false,false,false);});var p=this.parent;while(p){p._select(false,false,false);var isPartSel=false;for(var i=0;i<p.childList.length;i++){if(p.childList[i].bSelected||p.childList[i].hasSubSel){isPartSel=true;break;}}
p._setSubSel(isPartSel);p=p.parent;}}},_select:function(sel,fireEvents,deep){var opts=this.tree.options;if(this.data.isStatusNode)
return;if(this.bSelected==sel){return;}
if(fireEvents&&opts.onQuerySelect&&opts.onQuerySelect.call(this.span,sel,this)==false)
return;if(opts.selectMode==1&&sel){this.tree.visit(function(dtnode){if(dtnode.bSelected){dtnode._select(false,false,false);return false;}});}
this.bSelected=sel;if(sel){if(opts.persist)
this.tree.persistence.addSelect(this.data.key);$(this.span).addClass(opts.classNames.selected);if(deep&&opts.selectMode==3)
this._fixSelectionState();if(fireEvents&&opts.onSelect)
opts.onSelect.call(this.span,true,this);}else{if(opts.persist)
this.tree.persistence.clearSelect(this.data.key);$(this.span).removeClass(opts.classNames.selected);if(deep&&opts.selectMode==3)
this._fixSelectionState();if(fireEvents&&opts.onSelect)
opts.onSelect.call(this.span,false,this);}},select:function(sel){if(this.data.unselectable)
return this.bSelected;return this._select(sel!=false,true,true);},toggleSelect:function(){return this.select(!this.bSelected);},isSelected:function(){return this.bSelected;},_loadContent:function(){try{var opts=this.tree.options;this.tree.logDebug("_loadContent: start - %o",this);this.setLazyNodeStatus(DTNodeStatus_Loading);if(true==opts.onLazyRead.call(this.span,this)){this.setLazyNodeStatus(DTNodeStatus_Ok);this.tree.logDebug("_loadContent: succeeded - %o",this);}}catch(e){this.setLazyNodeStatus(DTNodeStatus_Error);this.tree.logWarning("_loadContent: failed - %o",e);}},_expand:function(bExpand){if(this.bExpanded==bExpand){return;}
var opts=this.tree.options;if(!bExpand&&this.getLevel()<opts.minExpandLevel){this.tree.logDebug("dtnode._expand(%o) forced expand - %o",bExpand,this);return;}
if(opts.onQueryExpand&&opts.onQueryExpand.call(this.span,bExpand,this)==false)
return;this.bExpanded=bExpand;if(opts.persist){if(bExpand)
this.tree.persistence.addExpand(this.data.key);else
this.tree.persistence.clearExpand(this.data.key);}
this.render(false);if(this.bExpanded&&this.parent&&opts.autoCollapse){var parents=this._parentList(false,true);for(var i=0;i<parents.length;i++)
parents[i].collapseSiblings();}
if(opts.activeVisible&&this.tree.activeNode&&!this.tree.activeNode.isVisible()){this.tree.activeNode.deactivate();}
if(bExpand&&this.data.isLazy&&this.childList==null&&!this.isLoading){this._loadContent();return;}
var fxDuration=opts.fx?(opts.fx.duration||200):0;if(this.childList){for(var i=0;i<this.childList.length;i++){var $child=$(this.childList[i].div);if(fxDuration){if(bExpand!=$child.is(':visible'))
$child.animate(opts.fx,fxDuration);}else{if(bExpand)
$child.show();else
$child.hide();}}}
if(opts.onExpand)
opts.onExpand.call(this.span,bExpand,this);},expand:function(flag){if(!this.childList&&!this.data.isLazy&&flag)
return;if(this.parent==null&&this.tree.options.minExpandLevel>0&&!flag)
return;this._expand(flag);},toggleExpand:function(){this.expand(!this.bExpanded);},collapseSiblings:function(){if(this.parent==null)
return;var ac=this.parent.childList;for(var i=0;i<ac.length;i++){if(ac[i]!==this&&ac[i].bExpanded)
ac[i]._expand(false);}},onClick:function(event){var targetType=this.getEventTargetType(event);if(targetType=="expander"){this.toggleExpand();this.focus();}else if(targetType=="checkbox"){this.toggleSelect();this.focus();}else{this._userActivate();this.span.getElementsByTagName("a")[0].focus();}
return false;},onDblClick:function(event){},onKeydown:function(event){var handled=true;switch(event.which){case 107:case 187:if(!this.bExpanded)this.toggleExpand();break;case 109:case 189:if(this.bExpanded)this.toggleExpand();break;case 32:this._userActivate();break;case 8:if(this.parent)
this.parent.focus();break;case 37:if(this.bExpanded){this.toggleExpand();this.focus();}else if(this.parent&&(this.tree.options.rootVisible||this.parent.parent)){this.parent.focus();}
break;case 39:if(!this.bExpanded&&(this.childList||this.data.isLazy)){this.toggleExpand();this.focus();}else if(this.childList){this.childList[0].focus();}
break;case 38:var sib=this.prevSibling();while(sib&&sib.bExpanded&&sib.childList)
sib=sib.childList[sib.childList.length-1];if(!sib&&this.parent&&(this.tree.options.rootVisible||this.parent.parent))
sib=this.parent;if(sib)sib.focus();break;case 40:var sib;if(this.bExpanded&&this.childList){sib=this.childList[0];}else{var parents=this._parentList(false,true);for(var i=parents.length-1;i>=0;i--){sib=parents[i].nextSibling();if(sib)break;}}
if(sib)sib.focus();break;default:handled=false;}
return!handled;},onKeypress:function(event){},onFocus:function(event){var opts=this.tree.options;if(event.type=="blur"||event.type=="focusout"){if(opts.onBlur)
opts.onBlur.call(this.span,this);if(this.tree.tnFocused)
$(this.tree.tnFocused.span).removeClass(opts.classNames.focused);this.tree.tnFocused=null;if(opts.persist)
$.cookie(opts.cookieId+"-focus","",opts.cookie);}else if(event.type=="focus"||event.type=="focusin"){if(this.tree.tnFocused&&this.tree.tnFocused!==this){this.tree.logDebug("dtnode.onFocus: out of sync: curFocus: %o",this.tree.tnFocused);$(this.tree.tnFocused.span).removeClass(opts.classNames.focused);}
this.tree.tnFocused=this;if(opts.onFocus)
opts.onFocus.call(this.span,this);$(this.tree.tnFocused.span).addClass(opts.classNames.focused);if(opts.persist)
$.cookie(opts.cookieId+"-focus",this.data.key,opts.cookie);}},visit:function(fn,data,includeSelf){var n=0;if(includeSelf==true){if(fn(this,data)==false)
return 1;n++;}
if(this.childList)
for(var i=0;i<this.childList.length;i++)
n+=this.childList[i].visit(fn,data,true);return n;},remove:function(){if(this===this.tree.root)
return false;return this.parent.removeChild(this);},removeChild:function(tn){var ac=this.childList;if(ac.length==1){if(tn!==ac[0])
throw"removeChild: invalid child";return this.removeChildren();}
if(tn===this.tree.activeNode)
tn.deactivate();if(this.tree.options.persist){if(tn.bSelected)
this.tree.persistence.clearSelect(tn.data.key);if(tn.bExpanded)
this.tree.persistence.clearExpand(tn.data.key);}
tn.removeChildren(true);this.div.removeChild(tn.div);for(var i=0;i<ac.length;i++){if(ac[i]===tn){this.childList.splice(i,1);delete tn;break;}}},removeChildren:function(isRecursiveCall,retainPersistence){var tree=this.tree;var ac=this.childList;if(ac){for(var i=0;i<ac.length;i++){var tn=ac[i];if(tn===tree.activeNode&&!retainPersistence)
tn.deactivate();if(this.tree.options.persist&&!retainPersistence){if(tn.bSelected)
this.tree.persistence.clearSelect(tn.data.key);if(tn.bExpanded)
this.tree.persistence.clearExpand(tn.data.key);}
tn.removeChildren(true,retainPersistence);this.div.removeChild(tn.div);delete tn;}
this.childList=null;}
if(!isRecursiveCall){this.isLoading=false;this.render(false,false);}},reload:function(force){if(this.parent==null)
return this.tree.reload();if(!this.data.isLazy)
throw"node.reload() requires lazy nodes.";if(this.bExpanded){this.expand(false);this.removeChildren();this.expand(true);}else{this.removeChildren();if(force)
this._loadContent();}},_addChildNode:function(dtnode,beforeNode){var tree=this.tree;var opts=tree.options;var pers=tree.persistence;dtnode.parent=this;if(this.childList==null){this.childList=[];}else if(!beforeNode){$(this.childList[this.childList.length-1].span).removeClass(opts.classNames.lastsib);}
if(beforeNode){var iBefore=$.inArray(beforeNode,this.childList);if(iBefore<0)
throw"<beforeNode> must be a child of <this>";this.childList.splice(iBefore,0,dtnode);}else{this.childList.push(dtnode);}
var isInitializing=tree.isInitializing();if(opts.persist&&pers.cookiesFound&&isInitializing){if(pers.activeKey==dtnode.data.key)
tree.activeNode=dtnode;if(pers.focusedKey==dtnode.data.key)
tree.focusNode=dtnode;dtnode.bExpanded=($.inArray(dtnode.data.key,pers.expandedKeyList)>=0);dtnode.bSelected=($.inArray(dtnode.data.key,pers.selectedKeyList)>=0);}else{if(dtnode.data.activate){tree.activeNode=dtnode;if(opts.persist)
pers.activeKey=dtnode.data.key;}
if(dtnode.data.focus){tree.focusNode=dtnode;if(opts.persist)
pers.focusedKey=dtnode.data.key;}
dtnode.bExpanded=(dtnode.data.expand==true);if(dtnode.bExpanded&&opts.persist)
pers.addExpand(dtnode.data.key);dtnode.bSelected=(dtnode.data.select==true);if(dtnode.bSelected&&opts.persist)
pers.addSelect(dtnode.data.key);}
if(opts.minExpandLevel>=dtnode.getLevel()){this.bExpanded=true;}
if(dtnode.bSelected&&opts.selectMode==3){var p=this;while(p){if(!p.hasSubSel)
p._setSubSel(true);p=p.parent;}}
if(tree.bEnableUpdate)
this.render(true,true);return dtnode;},addChild:function(obj,beforeNode){if(!obj||obj.length==0)
return;if(obj instanceof DynaTreeNode)
return this._addChildNode(obj,beforeNode);if(!obj.length)
obj=[obj];var prevFlag=this.tree.enableUpdate(false);var tnFirst=null;for(var i=0;i<obj.length;i++){var data=obj[i];var dtnode=this._addChildNode(new DynaTreeNode(this,this.tree,data),beforeNode);if(!tnFirst)tnFirst=dtnode;if(data.children)
dtnode.addChild(data.children,null);}
this.tree.enableUpdate(prevFlag);return tnFirst;},append:function(obj){this.tree.logWarning("node.append() is deprecated (use node.addChild() instead).");return this.addChild(obj,null);},appendAjax:function(ajaxOptions){this.removeChildren(false,true);this.setLazyNodeStatus(DTNodeStatus_Loading);var self=this;var orgSuccess=ajaxOptions.success;var orgError=ajaxOptions.error;var options=$.extend({},this.tree.options.ajaxDefaults,ajaxOptions,{success:function(data,textStatus){var prevPhase=self.tree.phase;self.tree.phase="init";self.addChild(data,null);self.tree.phase="postInit";self.setLazyNodeStatus(DTNodeStatus_Ok);if(orgSuccess)
orgSuccess.call(options,self);self.tree.phase=prevPhase;},error:function(XMLHttpRequest,textStatus,errorThrown){self.tree.logWarning("appendAjax failed:",textStatus,":\n",XMLHttpRequest,"\n",errorThrown);self.setLazyNodeStatus(DTNodeStatus_Error,{info:textStatus,tooltip:""+errorThrown});if(orgError)
orgError.call(options,self,XMLHttpRequest,textStatus,errorThrown);}});$.ajax(options);},lastentry:undefined}
var DynaTreeStatus=Class.create();DynaTreeStatus._getTreePersistData=function(cookieId,cookieOpts){var ts=new DynaTreeStatus(cookieId,cookieOpts);ts.read();return ts.toDict();}
getDynaTreePersistData=DynaTreeStatus._getTreePersistData;DynaTreeStatus.prototype={initialize:function(cookieId,cookieOpts){this._log("DynaTreeStatus: initialize");if(cookieId===undefined)
cookieId=$.ui.dynatree.defaults.cookieId;cookieOpts=$.extend({},$.ui.dynatree.defaults.cookie,cookieOpts);this.cookieId=cookieId;this.cookieOpts=cookieOpts;this.cookiesFound=undefined;this.activeKey=null;this.focusedKey=null;this.expandedKeyList=null;this.selectedKeyList=null;},_log:function(msg){Array.prototype.unshift.apply(arguments,["debug"]);_log.apply(this,arguments);},read:function(){this._log("DynaTreeStatus: read");this.cookiesFound=false;var cookie=$.cookie(this.cookieId+"-active");this.activeKey=(cookie==null)?"":cookie;if(cookie!=null)this.cookiesFound=true;cookie=$.cookie(this.cookieId+"-focus");this.focusedKey=(cookie==null)?"":cookie;if(cookie!=null)this.cookiesFound=true;cookie=$.cookie(this.cookieId+"-expand");this.expandedKeyList=(cookie==null)?[]:cookie.split(",");if(cookie!=null)this.cookiesFound=true;cookie=$.cookie(this.cookieId+"-select");this.selectedKeyList=(cookie==null)?[]:cookie.split(",");if(cookie!=null)this.cookiesFound=true;},write:function(){this._log("DynaTreeStatus: write");$.cookie(this.cookieId+"-active",(this.activeKey==null)?"":this.activeKey,this.cookieOpts);$.cookie(this.cookieId+"-focus",(this.focusedKey==null)?"":this.focusedKey,this.cookieOpts);$.cookie(this.cookieId+"-expand",(this.expandedKeyList==null)?"":this.expandedKeyList.join(","),this.cookieOpts);$.cookie(this.cookieId+"-select",(this.selectedKeyList==null)?"":this.selectedKeyList.join(","),this.cookieOpts);},addExpand:function(key){this._log("addExpand(%o)",key);if($.inArray(key,this.expandedKeyList)<0){this.expandedKeyList.push(key);$.cookie(this.cookieId+"-expand",this.expandedKeyList.join(","),this.cookieOpts);}},clearExpand:function(key){this._log("clearExpand(%o)",key);var idx=$.inArray(key,this.expandedKeyList);if(idx>=0){this.expandedKeyList.splice(idx,1);$.cookie(this.cookieId+"-expand",this.expandedKeyList.join(","),this.cookieOpts);}},addSelect:function(key){this._log("addSelect(%o)",key);if($.inArray(key,this.selectedKeyList)<0){this.selectedKeyList.push(key);$.cookie(this.cookieId+"-select",this.selectedKeyList.join(","),this.cookieOpts);}},clearSelect:function(key){this._log("clearSelect(%o)",key);var idx=$.inArray(key,this.selectedKeyList);if(idx>=0){this.selectedKeyList.splice(idx,1);$.cookie(this.cookieId+"-select",this.selectedKeyList.join(","),this.cookieOpts);}},isReloading:function(){return this.cookiesFound==true;},toDict:function(){return{cookiesFound:this.cookiesFound,activeKey:this.activeKey,focusedKey:this.activeKey,expandedKeyList:this.expandedKeyList,selectedKeyList:this.selectedKeyList};},lastentry:undefined};var DynaTree=Class.create();DynaTree.version="$Version: 0.5.3$";DynaTree.prototype={initialize:function($widget){this.phase="init";this.$widget=$widget;this.options=$widget.options;this.$tree=$widget.element;this.divTree=this.$tree.get(0);},_load:function(){var $widget=this.$widget;var opts=this.options;this.bEnableUpdate=true;this._nodeCount=1;this.activeNode=null;this.focusNode=null;if(opts.classNames!==$.ui.dynatree.defaults.classNames){opts.classNames=$.extend({},$.ui.dynatree.defaults.classNames,opts.classNames);}
if(!opts.imagePath){$("script").each(function(){if(this.src.search(_rexDtLibName)>=0){if(this.src.indexOf("/")>=0)
opts.imagePath=this.src.slice(0,this.src.lastIndexOf("/"))+"/skin/";else
opts.imagePath="skin/";return false;}});}
this.persistence=new DynaTreeStatus(opts.cookieId,opts.cookie);if(opts.persist){if(!$.cookie)
_log("warn","Please include jquery.cookie.js to use persistence.");this.persistence.read();}
this.logDebug("DynaTree.persistence: %o",this.persistence.toDict());this.cache={tagEmpty:"<span class='"+opts.classNames.empty+"'></span>",tagVline:"<span class='"+opts.classNames.vline+"'></span>",tagExpander:"<span class='"+opts.classNames.expander+"'></span>",tagConnector:"<span class='"+opts.classNames.connector+"'></span>",tagNodeIcon:"<span class='"+opts.classNames.nodeIcon+"'></span>",tagCheckbox:"<span class='"+opts.classNames.checkbox+"'></span>",lastentry:undefined};if(opts.children||(opts.initAjax&&opts.initAjax.url)||opts.initId)
$(this.divTree).empty();else if(this.divRoot)
$(this.divRoot).remove();this.tnRoot=new DynaTreeNode(null,this,{title:opts.title,key:"root"});this.tnRoot.data.isFolder=true;this.tnRoot.render(false,false);this.divRoot=this.tnRoot.div;this.divRoot.className=opts.classNames.container;this.divTree.appendChild(this.divRoot);var root=this.tnRoot;var isReloading=(opts.persist&&this.persistence.isReloading());var isLazy=false;var prevFlag=this.enableUpdate(false);this.logDebug("Dynatree._load(): read tree structure...");if(opts.children){root.addChild(opts.children);}else if(opts.initAjax&&opts.initAjax.url){isLazy=true;root.data.isLazy=true;this._reloadAjax();}else if(opts.initId){this._createFromTag(root,$("#"+opts.initId));}else{var $ul=this.$tree.find(">ul").hide();this._createFromTag(root,$ul);$ul.remove();}
this._checkConsistency();this.logDebug("Dynatree._load(): render nodes...");this.enableUpdate(prevFlag);this.logDebug("Dynatree._load(): bind events...");this.$widget.bind();this.logDebug("Dynatree._load(): postInit...");this.phase="postInit";if(opts.persist){this.persistence.write();}
if(this.focusNode&&this.focusNode.isVisible()){this.logDebug("Focus on init: %o",this.focusNode);this.focusNode.focus();}
if(!isLazy&&opts.onPostInit){opts.onPostInit.call(this,isReloading,false);}
this.phase="idle";},_reloadAjax:function(){var opts=this.options;if(!opts.initAjax||!opts.initAjax.url)
throw"tree.reload() requires 'initAjax' mode.";var pers=this.persistence;var ajaxOpts=$.extend({},opts.initAjax);if(ajaxOpts.addActiveKey)
ajaxOpts.data.activeKey=pers.activeKey;if(ajaxOpts.addFocusedKey)
ajaxOpts.data.focusedKey=pers.focusedKey;if(ajaxOpts.addExpandedKeyList)
ajaxOpts.data.expandedKeyList=pers.expandedKeyList.join(",");if(ajaxOpts.addSelectedKeyList)
ajaxOpts.data.selectedKeyList=pers.selectedKeyList.join(",");if(opts.onPostInit){if(ajaxOpts.success)
this.tree.logWarning("initAjax: success callback is ignored when onPostInit was specified.");if(ajaxOpts.error)
this.tree.logWarning("initAjax: error callback is ignored when onPostInit was specified.");var isReloading=pers.isReloading();ajaxOpts["success"]=function(dtnode){opts.onPostInit.call(dtnode.tree,isReloading,false);};ajaxOpts["error"]=function(dtnode){opts.onPostInit.call(dtnode.tree,isReloading,true);};}
this.logDebug("Dynatree._init(): send Ajax request...");this.tnRoot.appendAjax(ajaxOpts);},toString:function(){return"DynaTree '"+this.options.title+"'";},toDict:function(){return this.tnRoot.toDict(true);},getPersistData:function(){return this.persistence.toDict();},logDebug:function(msg){if(this.options.debugLevel>=2){Array.prototype.unshift.apply(arguments,["debug"]);_log.apply(this,arguments);}},logInfo:function(msg){if(this.options.debugLevel>=1){Array.prototype.unshift.apply(arguments,["info"]);_log.apply(this,arguments);}},logWarning:function(msg){Array.prototype.unshift.apply(arguments,["warn"]);_log.apply(this,arguments);},isInitializing:function(){return(this.phase=="init"||this.phase=="postInit");},isReloading:function(){return(this.phase=="init"||this.phase=="postInit")&&this.options.persist&&this.persistence.cookiesFound;},isUserEvent:function(){return(this.phase=="userEvent");},redraw:function(){this.logDebug("dynatree.redraw()...");this.tnRoot.render(true,true);this.logDebug("dynatree.redraw() done.");},reloadAjax:function(){this.logWarning("tree.reloadAjax() is deprecated since v0.5.2 (use reload() instead).");},reload:function(){this._load();},getRoot:function(){return this.tnRoot;},getNodeByKey:function(key){var el=document.getElementById(this.options.idPrefix+key);return(el&&el.dtnode)?el.dtnode:null;},getActiveNode:function(){return this.activeNode;},reactivate:function(setFocus){var node=this.activeNode;if(node){this.activeNode=null;node.activate();if(setFocus)
node.focus();}},getSelectedNodes:function(stopOnParents){var nodeList=[];this.tnRoot.visit(function(dtnode){if(dtnode.bSelected){nodeList.push(dtnode);if(stopOnParents==true)
return false;}});return nodeList;},activateKey:function(key){var dtnode=(key===null)?null:this.getNodeByKey(key);if(!dtnode){if(this.activeNode)
this.activeNode.deactivate();this.activeNode=null;return null;}
dtnode.focus();dtnode.activate();return dtnode;},selectKey:function(key,select){var dtnode=this.getNodeByKey(key);if(!dtnode)
return null;dtnode.select(select);return dtnode;},enableUpdate:function(bEnable){if(this.bEnableUpdate==bEnable)
return bEnable;this.bEnableUpdate=bEnable;if(bEnable)
this.redraw();return!bEnable;},visit:function(fn,data,includeRoot){return this.tnRoot.visit(fn,data,includeRoot);},_createFromTag:function(parentTreeNode,$ulParent){var self=this;$ulParent.find(">li").each(function(){var $li=$(this);var $liSpan=$li.find(">span:first");var title;if($liSpan.length){title=$liSpan.html();}else{title=$li.html();var iPos=title.search(/<ul/i);if(iPos>=0)
title=$.trim(title.substring(0,iPos));else
title=$.trim(title);}
var data={title:title,isFolder:$li.hasClass("folder"),isLazy:$li.hasClass("lazy"),expand:$li.hasClass("expanded"),select:$li.hasClass("selected"),activate:$li.hasClass("active"),focus:$li.hasClass("focused")};if($li.attr("title"))
data.tooltip=$li.attr("title");if($li.attr("id"))
data.key=$li.attr("id");if($li.attr("data")){var dataAttr=$.trim($li.attr("data"));if(dataAttr){if(dataAttr.charAt(0)!="{")
dataAttr="{"+dataAttr+"}"
try{$.extend(data,eval("("+dataAttr+")"));}catch(e){throw("Error parsing node data: "+e+"\ndata:\n'"+dataAttr+"'");}}}
childNode=parentTreeNode.addChild(data);var $ul=$li.find(">ul:first");if($ul.length){self._createFromTag(childNode,$ul);}});},_checkConsistency:function(){},lastentry:undefined};$.widget("ui.dynatree",{init:function(){_log("warn","ui.dynatree.init() was called; you should upgrade to ui.core.js v1.6 or higher.");return this._init();},_init:function(){if(parseFloat($.ui.version)<1.8){_log("warn","ui.dynatree._init() was called; you should upgrade to jquery.ui.core.js v1.8 or higher.");return this._create();}
_log("debug","ui.dynatree._init() was called; no current default functionality.");},_create:function(){if(parseFloat($.ui.version)>=1.8){this.options=$.extend(true,$[this.namespace][this.widgetName].defaults,this.options);}
logMsg("Dynatree._create(): version='%s', debugLevel=%o.",DynaTree.version,this.options.debugLevel);var opts=this.options;this.options.event+=".dynatree";var divTree=this.element.get(0);this.tree=new DynaTree(this);this.tree._load();this.tree.logDebug("Dynatree._create(): done.");},bind:function(){var $this=this.element;var o=this.options;this.unbind();function __getNodeFromElement(el){var iMax=5;while(el&&iMax--){if(el.dtnode)return el.dtnode;el=el.parentNode;};return null;}
var eventNames="click.dynatree dblclick.dynatree";if(o.keyboard)
eventNames+=" keypress.dynatree keydown.dynatree";$this.bind(eventNames,function(event){var dtnode=__getNodeFromElement(event.target);if(!dtnode)
return true;var prevPhase=dtnode.tree.phase;dtnode.tree.phase="userEvent";try{dtnode.tree.logDebug("bind(%o): dtnode: %o",event,dtnode);switch(event.type){case"click":return(o.onClick&&o.onClick(dtnode,event)===false)?false:dtnode.onClick(event);case"dblclick":return(o.onDblClick&&o.onDblClick(dtnode,event)===false)?false:dtnode.onDblClick(event);case"keydown":return(o.onKeydown&&o.onKeydown(dtnode,event)===false)?false:dtnode.onKeydown(event);case"keypress":return(o.onKeypress&&o.onKeypress(dtnode,event)===false)?false:dtnode.onKeypress(event);};}catch(e){var _=null;}finally{dtnode.tree.phase=prevPhase;}});function __focusHandler(event){event=arguments[0]=$.event.fix(event||window.event);var dtnode=__getNodeFromElement(event.target);return dtnode?dtnode.onFocus(event):false;}
var div=this.tree.divTree;if(div.addEventListener){div.addEventListener("focus",__focusHandler,true);div.addEventListener("blur",__focusHandler,true);}else{div.onfocusin=div.onfocusout=__focusHandler;}},unbind:function(){this.element.unbind(".dynatree");},enable:function(){this.bind();$.widget.prototype.enable.apply(this,arguments);},disable:function(){this.unbind();$.widget.prototype.disable.apply(this,arguments);},getTree:function(){return this.tree;},getRoot:function(){return this.tree.getRoot();},getActiveNode:function(){return this.tree.getActiveNode();},getSelectedNodes:function(){return this.tree.getSelectedNodes();},lastentry:undefined});$.ui.dynatree.getter="getTree getRoot getActiveNode getSelectedNodes";$.ui.dynatree.defaults={title:"Dynatree root",rootVisible:false,minExpandLevel:1,imagePath:null,children:null,initId:null,initAjax:null,autoFocus:true,keyboard:true,persist:false,autoCollapse:false,clickFolderMode:3,activeVisible:true,checkbox:false,selectMode:2,fx:null,onClick:null,onDblClick:null,onKeydown:null,onKeypress:null,onFocus:null,onBlur:null,onQueryActivate:null,onQuerySelect:null,onQueryExpand:null,onPostInit:null,onActivate:null,onDeactivate:null,onSelect:null,onExpand:null,onLazyRead:null,ajaxDefaults:{cache:false,dataType:"json"},strings:{loading:"Loading&#8230;",loadError:"Load error!"},idPrefix:"ui-dynatree-id-",cookieId:"dynatree",cookie:{expires:null},classNames:{container:"ui-dynatree-container",folder:"ui-dynatree-folder",document:"ui-dynatree-document",empty:"ui-dynatree-empty",vline:"ui-dynatree-vline",expander:"ui-dynatree-expander",connector:"ui-dynatree-connector",checkbox:"ui-dynatree-checkbox",nodeIcon:"ui-dynatree-icon",title:"ui-dynatree-title",nodeError:"ui-dynatree-statusnode-error",nodeWait:"ui-dynatree-statusnode-wait",hidden:"ui-dynatree-hidden",combinedExpanderPrefix:"ui-dynatree-exp-",combinedIconPrefix:"ui-dynatree-ico-",hasChildren:"ui-dynatree-has-children",active:"ui-dynatree-active",selected:"ui-dynatree-selected",expanded:"ui-dynatree-expanded",lazy:"ui-dynatree-lazy",focused:"ui-dynatree-focused",partsel:"ui-dynatree-partsel",lastsib:"ui-dynatree-lastsib"},debugLevel:1,lastentry:undefined};$.ui.dynatree.nodedatadefaults={title:null,key:null,isFolder:false,isLazy:false,tooltip:null,icon:null,addClass:null,activate:false,focus:false,expand:false,select:false,hideCheckbox:false,unselectable:false,children:null,lastentry:undefined};})(jQuery);var _rexDtLibName=/.*dynatree[^/]*\.js$/i;;
/*!
 * imagesLoaded PACKAGED v3.1.8
 * JavaScript is all like "You images are done yet or what?"
 * MIT License
 */

(function(){function e(){}function t(e,t){for(var n=e.length;n--;)if(e[n].listener===t)return n;return-1}function n(e){return function(){return this[e].apply(this,arguments)}}var i=e.prototype,r=this,o=r.EventEmitter;i.getListeners=function(e){var t,n,i=this._getEvents();if("object"==typeof e){t={};for(n in i)i.hasOwnProperty(n)&&e.test(n)&&(t[n]=i[n])}else t=i[e]||(i[e]=[]);return t},i.flattenListeners=function(e){var t,n=[];for(t=0;e.length>t;t+=1)n.push(e[t].listener);return n},i.getListenersAsObject=function(e){var t,n=this.getListeners(e);return n instanceof Array&&(t={},t[e]=n),t||n},i.addListener=function(e,n){var i,r=this.getListenersAsObject(e),o="object"==typeof n;for(i in r)r.hasOwnProperty(i)&&-1===t(r[i],n)&&r[i].push(o?n:{listener:n,once:!1});return this},i.on=n("addListener"),i.addOnceListener=function(e,t){return this.addListener(e,{listener:t,once:!0})},i.once=n("addOnceListener"),i.defineEvent=function(e){return this.getListeners(e),this},i.defineEvents=function(e){for(var t=0;e.length>t;t+=1)this.defineEvent(e[t]);return this},i.removeListener=function(e,n){var i,r,o=this.getListenersAsObject(e);for(r in o)o.hasOwnProperty(r)&&(i=t(o[r],n),-1!==i&&o[r].splice(i,1));return this},i.off=n("removeListener"),i.addListeners=function(e,t){return this.manipulateListeners(!1,e,t)},i.removeListeners=function(e,t){return this.manipulateListeners(!0,e,t)},i.manipulateListeners=function(e,t,n){var i,r,o=e?this.removeListener:this.addListener,s=e?this.removeListeners:this.addListeners;if("object"!=typeof t||t instanceof RegExp)for(i=n.length;i--;)o.call(this,t,n[i]);else for(i in t)t.hasOwnProperty(i)&&(r=t[i])&&("function"==typeof r?o.call(this,i,r):s.call(this,i,r));return this},i.removeEvent=function(e){var t,n=typeof e,i=this._getEvents();if("string"===n)delete i[e];else if("object"===n)for(t in i)i.hasOwnProperty(t)&&e.test(t)&&delete i[t];else delete this._events;return this},i.removeAllListeners=n("removeEvent"),i.emitEvent=function(e,t){var n,i,r,o,s=this.getListenersAsObject(e);for(r in s)if(s.hasOwnProperty(r))for(i=s[r].length;i--;)n=s[r][i],n.once===!0&&this.removeListener(e,n.listener),o=n.listener.apply(this,t||[]),o===this._getOnceReturnValue()&&this.removeListener(e,n.listener);return this},i.trigger=n("emitEvent"),i.emit=function(e){var t=Array.prototype.slice.call(arguments,1);return this.emitEvent(e,t)},i.setOnceReturnValue=function(e){return this._onceReturnValue=e,this},i._getOnceReturnValue=function(){return this.hasOwnProperty("_onceReturnValue")?this._onceReturnValue:!0},i._getEvents=function(){return this._events||(this._events={})},e.noConflict=function(){return r.EventEmitter=o,e},"function"==typeof define&&define.amd?define("eventEmitter/EventEmitter",[],function(){return e}):"object"==typeof module&&module.exports?module.exports=e:this.EventEmitter=e}).call(this),function(e){function t(t){var n=e.event;return n.target=n.target||n.srcElement||t,n}var n=document.documentElement,i=function(){};n.addEventListener?i=function(e,t,n){e.addEventListener(t,n,!1)}:n.attachEvent&&(i=function(e,n,i){e[n+i]=i.handleEvent?function(){var n=t(e);i.handleEvent.call(i,n)}:function(){var n=t(e);i.call(e,n)},e.attachEvent("on"+n,e[n+i])});var r=function(){};n.removeEventListener?r=function(e,t,n){e.removeEventListener(t,n,!1)}:n.detachEvent&&(r=function(e,t,n){e.detachEvent("on"+t,e[t+n]);try{delete e[t+n]}catch(i){e[t+n]=void 0}});var o={bind:i,unbind:r};"function"==typeof define&&define.amd?define("eventie/eventie",o):e.eventie=o}(this),function(e,t){"function"==typeof define&&define.amd?define(["eventEmitter/EventEmitter","eventie/eventie"],function(n,i){return t(e,n,i)}):"object"==typeof exports?module.exports=t(e,require("wolfy87-eventemitter"),require("eventie")):e.imagesLoaded=t(e,e.EventEmitter,e.eventie)}(window,function(e,t,n){function i(e,t){for(var n in t)e[n]=t[n];return e}function r(e){return"[object Array]"===d.call(e)}function o(e){var t=[];if(r(e))t=e;else if("number"==typeof e.length)for(var n=0,i=e.length;i>n;n++)t.push(e[n]);else t.push(e);return t}function s(e,t,n){if(!(this instanceof s))return new s(e,t);"string"==typeof e&&(e=document.querySelectorAll(e)),this.elements=o(e),this.options=i({},this.options),"function"==typeof t?n=t:i(this.options,t),n&&this.on("always",n),this.getImages(),a&&(this.jqDeferred=new a.Deferred);var r=this;setTimeout(function(){r.check()})}function f(e){this.img=e}function c(e){this.src=e,v[e]=this}var a=e.jQuery,u=e.console,h=u!==void 0,d=Object.prototype.toString;s.prototype=new t,s.prototype.options={},s.prototype.getImages=function(){this.images=[];for(var e=0,t=this.elements.length;t>e;e++){var n=this.elements[e];"IMG"===n.nodeName&&this.addImage(n);var i=n.nodeType;if(i&&(1===i||9===i||11===i))for(var r=n.querySelectorAll("img"),o=0,s=r.length;s>o;o++){var f=r[o];this.addImage(f)}}},s.prototype.addImage=function(e){var t=new f(e);this.images.push(t)},s.prototype.check=function(){function e(e,r){return t.options.debug&&h&&u.log("confirm",e,r),t.progress(e),n++,n===i&&t.complete(),!0}var t=this,n=0,i=this.images.length;if(this.hasAnyBroken=!1,!i)return this.complete(),void 0;for(var r=0;i>r;r++){var o=this.images[r];o.on("confirm",e),o.check()}},s.prototype.progress=function(e){this.hasAnyBroken=this.hasAnyBroken||!e.isLoaded;var t=this;setTimeout(function(){t.emit("progress",t,e),t.jqDeferred&&t.jqDeferred.notify&&t.jqDeferred.notify(t,e)})},s.prototype.complete=function(){var e=this.hasAnyBroken?"fail":"done";this.isComplete=!0;var t=this;setTimeout(function(){if(t.emit(e,t),t.emit("always",t),t.jqDeferred){var n=t.hasAnyBroken?"reject":"resolve";t.jqDeferred[n](t)}})},a&&(a.fn.imagesLoaded=function(e,t){var n=new s(this,e,t);return n.jqDeferred.promise(a(this))}),f.prototype=new t,f.prototype.check=function(){var e=v[this.img.src]||new c(this.img.src);if(e.isConfirmed)return this.confirm(e.isLoaded,"cached was confirmed"),void 0;if(this.img.complete&&void 0!==this.img.naturalWidth)return this.confirm(0!==this.img.naturalWidth,"naturalWidth"),void 0;var t=this;e.on("confirm",function(e,n){return t.confirm(e.isLoaded,n),!0}),e.check()},f.prototype.confirm=function(e,t){this.isLoaded=e,this.emit("confirm",this,t)};var v={};return c.prototype=new t,c.prototype.check=function(){if(!this.isChecked){var e=new Image;n.bind(e,"load",this),n.bind(e,"error",this),e.src=this.src,this.isChecked=!0}},c.prototype.handleEvent=function(e){var t="on"+e.type;this[t]&&this[t](e)},c.prototype.onload=function(e){this.confirm(!0,"onload"),this.unbindProxyEvents(e)},c.prototype.onerror=function(e){this.confirm(!1,"onerror"),this.unbindProxyEvents(e)},c.prototype.confirm=function(e,t){this.isConfirmed=!0,this.isLoaded=e,this.emit("confirm",this,t)},c.prototype.unbindProxyEvents=function(e){n.unbind(e.target,"load",this),n.unbind(e.target,"error",this)},s});;
(function(window, document, undefined) {
    'use strict';
    (function(factory) {
        if (typeof define === 'function' && define.amd) {
            // Register as an anonymous AMD module.
            define(['jquery'], factory);
        } else if (typeof exports === 'object') {
            // Node/CommonJS
            module.exports = factory(require('jquery'));
        } else {
            // Browser globals
            factory(window.jQuery);
        }
    }(function($) {
        // Create the defaults once
        var pluginName = 'webuiPopover';
        var pluginClass = 'webui-popover';
        var pluginType = 'webui.popover';
        var defaults = {
            placement: 'auto',
            container: null,
            width: 'auto',
            height: 'auto',
            trigger: 'click', //hover,click,sticky,manual
            style: '',
            delay: {
                show: null,
                hide: 300
            },
            async: {
                type: 'GET',
                before: null, //function(that, xhr){}
                success: null, //function(that, xhr){}
                error: null //function(that, xhr, data){}
            },
            cache: true,
            multi: false,
            arrow: true,
            title: '',
            content: '',
            closeable: false,
            padding: true,
            url: '',
            type: 'html',
            direction: '', // ltr,rtl
            animation: null,
            template: '<div class="webui-popover">' +
                '<div class="webui-arrow"></div>' +
                '<div class="webui-popover-inner">' +
                '<a href="#" class="close"></a>' +
                '<h3 class="webui-popover-title"></h3>' +
                '<div class="webui-popover-content"><i class="icon-refresh"></i> <p>&nbsp;</p></div>' +
                '</div>' +
                '</div>',
            backdrop: false,
            dismissible: true,
            onShow: null,
            onHide: null,
            abortXHR: true,
            autoHide: false,
            offsetTop: 0,
            offsetLeft: 0,
            iframeOptions: {
                frameborder: '0',
                allowtransparency: 'true',
                id: '',
                name: '',
                scrolling: '',
                onload: '',
                height: '',
                width: ''
            },
            hideEmpty: false
        };

        var rtlClass = pluginClass + '-rtl';
        var _srcElements = [];
        var backdrop = $('<div class="webui-popover-backdrop"></div>');
        var _globalIdSeed = 0;
//        var _isBodyEventHandled = false;
        var _offsetOut = -2000; // the value offset out of the screen
        var isEnabled = true;
        var $document = $(document);

        var toNumber = function(numeric, fallback) {
            return isNaN(numeric) ? (fallback || 0) : Number(numeric);
        };

        var getPopFromElement = function($element) {
            return $element.data('plugin_' + pluginName);
        };

        var hideAllPop = function() {
            var pop = null;
            for (var i = 0; i < _srcElements.length; i++) {
                pop = getPopFromElement(_srcElements[i]);
                if (pop) {
                    pop.hide(true);
                }
            }
            $document.trigger('hiddenAll.' + pluginType);
        };

        var destroyChildren = function (parentSelector) {
            var children = _srcElements
                .reduce(function (acc, e, i) {
                    if (!$.contains(document, e[0]) || e.closest(parentSelector).length) {
                        acc.unshift({ index: i, pop: getPopFromElement(e) });
                    }
                    return acc;
                }, []);

             children.forEach(function (child) {
                _srcElements.splice(child.index, 1);

                if (!child.pop) return;

                child.pop.hide();
                child.pop.$element.data('plugin_' + pluginName, null);

                if (child.pop.getTrigger() === 'click') {
                    child.pop.$element.off('click');
                } else if (child.pop.getTrigger() === 'hover') {
                    child.pop.$element.off('mouseenter mouseleave');
                }

                if (child.pop.$target) {
                    child.pop.$target.remove();
                }
            });
        };

        var isMobile = ('ontouchstart' in document.documentElement) && (/Mobi/.test(navigator.userAgent));

        var pointerEventToXY = function(e) {
            var out = {
                x: 0,
                y: 0
            };
            if (e.type === 'touchstart' || e.type === 'touchmove' || e.type === 'touchend' || e.type === 'touchcancel') {
                var touch = e.originalEvent.touches[0] || e.originalEvent.changedTouches[0];
                out.x = touch.pageX;
                out.y = touch.pageY;
            } else if (e.type === 'mousedown' || e.type === 'mouseup' || e.type === 'click') {
                out.x = e.pageX;
                out.y = e.pageY;
            }
            return out;
        };

        // The actual plugin constructor
        function WebuiPopover(element, options) {
            this.$element = $(element);
            if (options) {
                if ($.type(options.delay) === 'string' || $.type(options.delay) === 'number') {
                    options.delay = {
                        show: options.delay,
                        hide: options.delay
                    }; // bc break fix
                }
            }
            this.options = $.extend({}, defaults, options);
            this._defaults = defaults;
            this._name = pluginName;
            this._targetclick = false;
            this.init();
            _srcElements.push(this.$element);

        }

        WebuiPopover.prototype = {
            //init webui popover
            init: function() {
                if (this.getTrigger() !== 'manual') {
                    //init the event handlers
                    if (this.getTrigger() === 'click' || isMobile) {
                        this.$element.off('click touchend').on('click touchend', $.proxy(this.toggle, this));
                    } else if (this.getTrigger() === 'hover') {
                        this.$element
                            .off('mouseenter mouseleave click')
                            .on('mouseenter', $.proxy(this.mouseenterHandler, this))
                            .on('mouseleave', $.proxy(this.mouseleaveHandler, this));
                    }
                }
                this._poped = false;
                this._inited = true;
                this._opened = false;
                this._idSeed = _globalIdSeed;
                // normalize container
                this.options.container = $(this.options.container || document.body).first();

                if (this.options.backdrop) {
                    backdrop.appendTo(this.options.container).hide();
                }
                _globalIdSeed++;
                if (this.getTrigger() === 'sticky') {
                    this.show();
                }

            },
            /* api methods and actions */
            destroy: function() {
                var index = -1;

                for (var i = 0; i < _srcElements.length; i++) {
                    if (_srcElements[i] === this.$element) {
                        index = i;
                        break;
                    }
                }

                _srcElements.splice(index, 1);


                this.hide();
                this.$element.data('plugin_' + pluginName, null);
                if (this.getTrigger() === 'click') {
                    this.$element.off('click');
                } else if (this.getTrigger() === 'hover') {
                    this.$element.off('mouseenter mouseleave');
                }
                if (this.$target) {
                    this.$target.remove();
                }
            },
            /*
                param: force    boolean value, if value is true then force hide the popover
                param: event    dom event,
            */
            hide: function(force, event) {
                if (!force && this.getTrigger() === 'sticky') {
                    return;
                }
                if (!this._opened) {
                    return;
                }

                if (event) {
                    event.preventDefault();
                    event.stopPropagation();
                }

                if (this.xhr && this.options.abortXHR === true) {
                    this.xhr.abort();
                    this.xhr = null;
                }


                var e = $.Event('hide.' + pluginType);
                this.$element.trigger(e, [this.$target]);
                if (this.$target) {
                    this.$target.removeClass('in').addClass(this.getHideAnimation());
                    var that = this;
                    setTimeout(function() {
                        that.$target.hide();
                        if (!that.getCache()) {
                            that.$target.remove();
                        }
                    }, that.getHideDelay());
                }
                if (this.options.backdrop) {
                    backdrop.hide();
                }
                this._opened = false;
                this.$element.trigger('hidden.' + pluginType, [this.$target]);

                if (this.options.onHide) {
                    this.options.onHide(this.$target);
                }

            },
            resetAutoHide: function() {
                var that = this;
                var autoHide = that.getAutoHide();
                if (autoHide) {
                    if (that.autoHideHandler) {
                        clearTimeout(that.autoHideHandler);
                    }
                    that.autoHideHandler = setTimeout(function() {
                        that.hide();
                    }, autoHide);
                }
            },
            toggle: function(e) {
                if (e) {
                    e.preventDefault();
                    e.stopPropagation();
                }
                this[this.getTarget().hasClass('in') ? 'hide' : 'show']();
            },
            hideAll: function() {
                hideAllPop();
            },
            /*core method ,show popover */
            show: function () {
                if (!isEnabled) {
                    return;
                }
                if (this._opened) {
                    return;
                }
                //removeAllTargets();
                var
                    $target = this.getTarget().removeClass().addClass(pluginClass).addClass(this._customTargetClass);
                if (!this.options.multi) {
                    this.hideAll();
                }

                // use cache by default, if not cache setted  , reInit the contents
                if (!this.getCache() || !this._poped || this.content === '') {
                    this.content = '';
                    this.setTitle(this.getTitle());
                    if (!this.options.closeable) {
                        $target.find('.close').off('click').remove();
                    }

                    if (!this.isAsync()) {
                        this.setContent(this.getContent());
                    } else {
                        this.setContentASync(this.options.content);
                    }

                    if (this.canEmptyHide() && this.content === '') {
                        return;
                    }
                    $target.show();
                }

                this.displayContent();

                if (this.options.onShow) {
                    this.options.onShow($target);
                }

                this.bindBodyEvents();
                if (this.options.backdrop) {
                    backdrop.show();
                }
                this._opened = true;
                this.resetAutoHide();
            },
            displayContent: function() {
                var
                //element postion
                    elementPos = this.getElementPosition(),
                    //target postion
                    $target = this.getTarget().removeClass().addClass(pluginClass).addClass(this._customTargetClass),
                    //target content
                    $targetContent = this.getContentElement(),
                    //target Width
                    targetWidth = $target[0].offsetWidth,
                    //target Height
                    targetHeight = $target[0].offsetHeight,
                    //placement
                    placement = 'bottom',
                    e = $.Event('show.' + pluginType);

                if (this.canEmptyHide()) {

                    var content = $targetContent.children().html();
                    if (content !== null && content.trim().length === 0) {
                        return;
                    }
                }

                //if (this.hasContent()){
                this.$element.trigger(e, [$target]);
                //}
                // support width as data attribute
                var optWidth = this.$element.data('width') || this.options.width;
                if (optWidth === '') {
                    optWidth = this._defaults.width;
                }

                if (optWidth !== 'auto') {
                    $target.width(optWidth);
                }

                // support height as data attribute
                var optHeight = this.$element.data('height') || this.options.height;
                if (optHeight === '') {
                    optHeight = this._defaults.height;
                }

                if (optHeight !== 'auto') {
                    $targetContent.height(optHeight);
                }

                if (this.options.style) {
                    this.$target.addClass(pluginClass + '-' + this.options.style);
                }

                //check rtl
                if (this.options.direction === 'rtl' && !$targetContent.hasClass(rtlClass)) {
                    $targetContent.addClass(rtlClass);
                }

                //init the popover and insert into the document body
                if (!this.options.arrow) {
                    $target.find('.webui-arrow').remove();
                }
                $target.detach().css({
                    top: _offsetOut,
                    left: _offsetOut,
                    display: 'block'
                });

                if (this.getAnimation()) {
                    $target.addClass(this.getAnimation());
                }
                $target.appendTo(this.options.container);


                placement = this.getPlacement(elementPos);

                //This line is just for compatible with knockout custom binding
                this.$element.trigger('added.' + pluginType);

                this.initTargetEvents();

                if (!this.options.padding) {
                    if (this.options.height !== 'auto') {
                        $targetContent.css('height', $targetContent.outerHeight());
                    }
                    this.$target.addClass('webui-no-padding');
                }

                // add maxHeight and maxWidth support
                if (this.options.maxHeight) {
                    $targetContent.css('maxHeight', this.options.maxHeight);
                }
               
                if (this.options.maxWidth) {
                    $targetContent.css('maxWidth', this.options.maxWidth);
                }              

                targetWidth = $target[0].offsetWidth;
                targetHeight = $target[0].offsetHeight;

                var postionInfo = this.getTargetPositin(elementPos, placement, targetWidth, targetHeight);

                this.$target.css(postionInfo.position).addClass(placement).addClass('in');

                if (this.options.type === 'iframe') {
                    var $iframe = $target.find('iframe');
                    var iframeWidth = $target.width();
                    var iframeHeight = $iframe.parent().height();

                    if (this.options.iframeOptions.width !== '' && this.options.iframeOptions.width !== 'auto') {
                        iframeWidth = this.options.iframeOptions.width;
                    }

                    if (this.options.iframeOptions.height !== '' && this.options.iframeOptions.height !== 'auto') {
                        iframeHeight = this.options.iframeOptions.height;
                    }

                    $iframe.width(iframeWidth).height(iframeHeight);
                }




                if (!this.options.arrow) {
                    this.$target.css({
                        'margin': 0
                    });
                }
                if (this.options.arrow) {
                    var $arrow = this.$target.find('.webui-arrow');
                    $arrow.removeAttr('style');

                    //prevent arrow change by content size
                    if (placement === 'left' || placement === 'right') {
                        $arrow.css({
                            top: this.$target.height() / 2
                        });
                    } else if (placement === 'top' || placement === 'bottom') {
                        $arrow.css({
                            left: this.$target.width() / 2
                        });
                    }

                    if (postionInfo.arrowOffset) {
                        //hide the arrow if offset is negative
                        if (postionInfo.arrowOffset.left === -1 || postionInfo.arrowOffset.top === -1) {
                            $arrow.hide();
                        } else {
                            $arrow.css(postionInfo.arrowOffset);
                        }
                    }

                }
                this._poped = true;
                this.$element.trigger('shown.' + pluginType, [this.$target]);
            },

            isTargetLoaded: function() {
                return this.getTarget().find('i.glyphicon-refresh').length === 0;
            },

            /*getter setters */
            getTriggerElement: function() {
                return this.$element;
            },
            getTarget: function() {
                if (!this.$target) {
                    var id = pluginName + this._idSeed;
                    this.$target = $(this.options.template)
                        .attr('id', id)
                        .data('trigger-element', this.getTriggerElement());
                    this._customTargetClass = this.$target.attr('class') !== pluginClass ? this.$target.attr('class') : null;
                    this.getTriggerElement().attr('data-target', id);
                }
                return this.$target;
            },
            getTitleElement: function() {
                return this.getTarget().find('.' + pluginClass + '-title');
            },
            getContentElement: function() {
                if (!this.$contentElement) {
                    this.$contentElement = this.getTarget().find('.' + pluginClass + '-content');
                }
                return this.$contentElement;
            },
            getTitle: function() {
                return this.$element.attr('data-title') || this.options.title || this.$element.attr('title');
            },
            getUrl: function() {
                return this.$element.attr('data-url') || this.options.url;
            },
            getAutoHide: function() {
                return this.$element.attr('data-auto-hide') || this.options.autoHide;
            },
            getOffsetTop: function() {
                return toNumber(this.$element.attr('data-offset-top')) || this.options.offsetTop;
            },
            getOffsetLeft: function() {
                return toNumber(this.$element.attr('data-offset-left')) || this.options.offsetLeft;
            },
            getCache: function() {
                var dataAttr = this.$element.attr('data-cache');
                if (typeof(dataAttr) !== 'undefined') {
                    switch (dataAttr.toLowerCase()) {
                        case 'true':
                        case 'yes':
                        case '1':
                            return true;
                        case 'false':
                        case 'no':
                        case '0':
                            return false;
                    }
                }
                return this.options.cache;
            },
            getTrigger: function() {
                return this.$element.attr('data-trigger') || this.options.trigger;
            },
            getDelayShow: function() {
                var dataAttr = this.$element.attr('data-delay-show');
                if (typeof(dataAttr) !== 'undefined') {
                    return dataAttr;
                }
                return this.options.delay.show === 0 ? 0 : this.options.delay.show || 100;
            },
            getHideDelay: function() {
                var dataAttr = this.$element.attr('data-delay-hide');
                if (typeof(dataAttr) !== 'undefined') {
                    return dataAttr;
                }
                return this.options.delay.hide === 0 ? 0 : this.options.delay.hide || 100;
            },
            getAnimation: function() {
                var dataAttr = this.$element.attr('data-animation');
                return dataAttr || this.options.animation;
            },
            getHideAnimation: function() {
                var ani = this.getAnimation();
                return ani ? ani + '-out' : 'out';
            },
            setTitle: function(title) {
                var $titleEl = this.getTitleElement();
                if (title) {
                    //check rtl
                    if (this.options.direction === 'rtl' && !$titleEl.hasClass(rtlClass)) {
                        $titleEl.addClass(rtlClass);
                    }
                    $titleEl.html(title);
                } else {
                    $titleEl.remove();
                }
            },
            hasContent: function() {
                return this.getContent();
            },
            canEmptyHide: function() {
                return this.options.hideEmpty && this.options.type === 'html';
            },
            getIframe: function() {
                var $iframe = $('<iframe></iframe>').attr('src', this.getUrl());
                var self = this;
                $.each(this._defaults.iframeOptions, function(opt) {
                    if (typeof self.options.iframeOptions[opt] !== 'undefined') {
                        $iframe.attr(opt, self.options.iframeOptions[opt]);
                    }
                });

                return $iframe;
            },
            getContent: function() {
                if (this.getUrl()) {
                    switch (this.options.type) {
                        case 'iframe':
                            this.content = this.getIframe();
                            break;
                        case 'html':
                            try {
                                this.content = $(this.getUrl());
                                if (!this.content.is(':visible')) {
                                    this.content.show();
                                }
                            } catch (error) {
                                throw new Error('Unable to get popover content. Invalid selector specified.');
                            }
                            break;
                    }
                } else if (!this.content) {
                    var content = '';
                    if ($.isFunction(this.options.content)) {
                        content = this.options.content.apply(this.$element[0], [this]);
                    } else {
                        content = this.options.content;
                    }
                    this.content = this.$element.attr('data-content') || content;
                    if (!this.content) {
                        var $next = this.$element.next();

                        if ($next && $next.hasClass(pluginClass + '-content')) {
                            this.content = $next;
                        }
                    }
                }
                return this.content;
            },
            setContent: function(content) {
                var $target = this.getTarget();
                var $ct = this.getContentElement();
                if (typeof content === 'string') {
                    $ct.html(content);
                } else if (content instanceof $) {
                    $ct.html('');
                    //Don't want to clone too many times.
                    if (!this.options.cache) {
                        content.clone(true, true).removeClass(pluginClass + '-content').appendTo($ct);
                    } else {
                        content.removeClass(pluginClass + '-content').appendTo($ct);
                    }
                }
                this.$target = $target;
            },
            isAsync: function() {
                return this.options.type === 'async';
            },
            setContentASync: function(content) {
                var that = this;
                if (this.xhr) {
                    return;
                }
                this.xhr = $.ajax({
                    url: this.getUrl(),
                    type: this.options.async.type,
                    cache: this.getCache(),
                    beforeSend: function(xhr) {
                        if (that.options.async.before) {
                            that.options.async.before(that, xhr);
                        }
                    },
                    success: function(data) {
                        that.bindBodyEvents();
                        if (content && $.isFunction(content)) {
                            that.content = content.apply(that.$element[0], [data]);
                        } else {
                            that.content = data;
                        }
                        that.setContent(that.content);
                        var $targetContent = that.getContentElement();
                        $targetContent.removeAttr('style');
                        that.displayContent();
                        if (that.options.async.success) {
                            that.options.async.success(that, data);
                        }
                    },
                    complete: function() {
                        that.xhr = null;
                    },
                    error: function(xhr, data) {
                        if (that.options.async.error) {
                            that.options.async.error(that, xhr, data);
                        }
                    }
                });
            },

            bindBodyEvents: function () {
                /*
                if (_isBodyEventHandled) {
                    return;
                }   
                */
                if (this.options.dismissible && this.getTrigger() === 'click') {
                    /*
                    $document.off('keyup.webui-popover').on('keyup.webui-popover', $.proxy(this.escapeHandler, this));
                    $document.off('click.webui-popover touchend.webui-popover')
                        .on('click.webui-popover touchend.webui-popover', $.proxy(this.bodyClickHandler, this));  
                    */
                    $document.off('keyup.webui-popover' + this._idSeed).on('keyup.webui-popover' + this._idSeed, $.proxy(this.escapeHandler, this));
                      $document.off('click.webui-popover' + this._idSeed).on('click.webui-popover' + this._idSeed, $.proxy(this.bodyClickHandler, this));
                } else if (this.getTrigger() === 'hover') {
                    /*
                    $document.off('touchend.webui-popover')
                        .on('touchend.webui-popover', $.proxy(this.bodyClickHandler, this));
                    */
                    $document.off('touchend.webui-popover' + this._idSeed).on('touchend.webui-popover' + this._idSeed, $.proxy(this.bodyClickHandler, this));
                }
            },

            /* event handlers */
            mouseenterHandler: function() {
                var self = this;
                if (self._timeout) {
                    clearTimeout(self._timeout);
                }
                self._enterTimeout = setTimeout(function() {
                    if (!self.getTarget().is(':visible')) {
                        self.show();
                    }
                }, this.getDelayShow());
            },
            mouseleaveHandler: function() {
                var self = this;
                clearTimeout(self._enterTimeout);
                //key point, set the _timeout  then use clearTimeout when mouse leave
                self._timeout = setTimeout(function() {
                    self.hide();
                }, this.getHideDelay());
            },
            escapeHandler: function(e) {
                if (e.keyCode === 27) {
//                    this.hideAll();
                    this.hide();
                }
            },
            bodyClickHandler: function (e) {
//                _isBodyEventHandled = true;
                var canHide = true;
                for (var i = 0; i < _srcElements.length; i++) {
                    var pop = getPopFromElement(_srcElements[i]);
                    if (pop && pop._opened) {
                        var offset = pop.getTarget().offset();
                        var popX1 = offset.left;
                        var popY1 = offset.top;
                        var popX2 = offset.left + pop.getTarget().width();
                        var popY2 = offset.top + pop.getTarget().height();
                        var pt = pointerEventToXY(e);
                        var inPop = pt.x >= popX1 && pt.x <= popX2 && pt.y >= popY1 && pt.y <= popY2;
                        if (inPop) {
                            canHide = false;
                            break;
                        }
                    }
                }
                if (canHide) {
//                    hideAllPop();
                    this.hide();
                }
            },
            //reset and init the target events;
            initTargetEvents: function() {
                if (this.getTrigger() === 'hover') {
                    this.$target
                        .off('mouseenter mouseleave')
                        .on('mouseenter', $.proxy(this.mouseenterHandler, this))
                        .on('mouseleave', $.proxy(this.mouseleaveHandler, this));
                }
                this.$target.find('.close').off('click').on('click', $.proxy(this.hide, this, true));
                //this.$target.off('click.webui-popover').on('click.webui-popover', $.proxy(this.targetClickHandler, this));
            },
            /* utils methods */
            //caculate placement of the popover
            getPlacement: function(pos) {
                var
                    placement,
                    container = this.options.container,
                    clientWidth = container.innerWidth(),
                    clientHeight = container.innerHeight(),
                    scrollTop = container.scrollTop(),
                    scrollLeft = container.scrollLeft(),
                    pageX = Math.max(0, pos.left - scrollLeft),
                    pageY = Math.max(0, pos.top - scrollTop);
                //arrowSize = 20;

                //if placement equals auto，caculate the placement by element information;
                if (typeof(this.options.placement) === 'function') {
                    placement = this.options.placement.call(this, this.getTarget()[0], this.$element[0]);
                } else {
                    placement = this.$element.data('placement') || this.options.placement;
                }

                var isH = placement === 'horizontal';
                var isV = placement === 'vertical';
                var detect = placement === 'auto' || isH || isV;

                if (detect) {
                    if (pageX < clientWidth / 3) {
                        if (pageY < clientHeight / 3) {
                            placement = isH ? 'right-bottom' : 'bottom-right';
                        } else if (pageY < clientHeight * 2 / 3) {
                            if (isV) {
                                placement = pageY <= clientHeight / 2 ? 'bottom-right' : 'top-right';
                            } else {
                                placement = 'right';
                            }
                        } else {
                            placement = isH ? 'right-top' : 'top-right';
                        }
                        //placement= pageY>targetHeight+arrowSize?'top-right':'bottom-right';
                    } else if (pageX < clientWidth * 2 / 3) {
                        if (pageY < clientHeight / 3) {
                            if (isH) {
                                placement = pageX <= clientWidth / 2 ? 'right-bottom' : 'left-bottom';
                            } else {
                                placement = 'bottom';
                            }
                        } else if (pageY < clientHeight * 2 / 3) {
                            if (isH) {
                                placement = pageX <= clientWidth / 2 ? 'right' : 'left';
                            } else {
                                placement = pageY <= clientHeight / 2 ? 'bottom' : 'top';
                            }
                        } else {
                            if (isH) {
                                placement = pageX <= clientWidth / 2 ? 'right-top' : 'left-top';
                            } else {
                                placement = 'top';
                            }
                        }
                    } else {
                        //placement = pageY>targetHeight+arrowSize?'top-left':'bottom-left';
                        if (pageY < clientHeight / 3) {
                            placement = isH ? 'left-bottom' : 'bottom-left';
                        } else if (pageY < clientHeight * 2 / 3) {
                            if (isV) {
                                placement = pageY <= clientHeight / 2 ? 'bottom-left' : 'top-left';
                            } else {
                                placement = 'left';
                            }
                        } else {
                            placement = isH ? 'left-top' : 'top-left';
                        }
                    }
                } else if (placement === 'auto-top') {
                    if (pageX < clientWidth / 3) {
                        placement = 'top-right';
                    } else if (pageX < clientWidth * 2 / 3) {
                        placement = 'top';
                    } else {
                        placement = 'top-left';
                    }
                } else if (placement === 'auto-bottom') {
                    if (pageX < clientWidth / 3) {
                        placement = 'bottom-right';
                    } else if (pageX < clientWidth * 2 / 3) {
                        placement = 'bottom';
                    } else {
                        placement = 'bottom-left';
                    }
                } else if (placement === 'auto-left') {
                    if (pageY < clientHeight / 3) {
                        placement = 'left-top';
                    } else if (pageY < clientHeight * 2 / 3) {
                        placement = 'left';
                    } else {
                        placement = 'left-bottom';
                    }
                } else if (placement === 'auto-right') {
                    if (pageY < clientHeight / 3) {
                        placement = 'right-top';
                    } else if (pageY < clientHeight * 2 / 3) {
                        placement = 'right';
                    } else {
                        placement = 'right-bottom';
                    }
                }
                return placement;
            },
            getElementPosition: function() {
                // If the container is the body or normal conatiner, just use $element.offset()
                var elRect = this.$element[0].getBoundingClientRect();
                var container = this.options.container;
                var cssPos = container.css('position');

                if (container.is(document.body) || cssPos === 'static') {
                    return $.extend({}, this.$element.offset(), {
                        width: this.$element[0].offsetWidth || elRect.width,
                        height: this.$element[0].offsetHeight || elRect.height
                    });
                    // Else fixed container need recalculate the  position
                } else if (cssPos === 'fixed') {
                    var containerRect = container[0].getBoundingClientRect();
                    return {
                        top: elRect.top - containerRect.top + container.scrollTop(),
                        left: elRect.left - containerRect.left + container.scrollLeft(),
                        width: elRect.width,
                        height: elRect.height
                    };
                } else if (cssPos === 'relative') {
                    return {
                        top: this.$element.offset().top - container.offset().top,
                        left: this.$element.offset().left - container.offset().left,
                        width: this.$element[0].offsetWidth || elRect.width,
                        height: this.$element[0].offsetHeight || elRect.height
                    };
                }
            },

            getTargetPositin: function(elementPos, placement, targetWidth, targetHeight) {
                var pos = elementPos,
                    container = this.options.container,
                    //clientWidth = container.innerWidth(),
                    //clientHeight = container.innerHeight(),
                    elementW = this.$element.outerWidth(),
                    elementH = this.$element.outerHeight(),
                    scrollTop = document.documentElement.scrollTop + container.scrollTop(),
                    scrollLeft = document.documentElement.scrollLeft + container.scrollLeft(),
                    position = {},
                    arrowOffset = null,
                    arrowSize = this.options.arrow ? 20 : 0,
                    padding = 10,
                    fixedW = elementW < arrowSize + padding ? arrowSize : 0,
                    fixedH = elementH < arrowSize + padding ? arrowSize : 0,
                    refix = 0,
                    pageH = document.documentElement.clientHeight + scrollTop,
                    pageW = document.documentElement.clientWidth + scrollLeft;

                var validLeft = pos.left + pos.width / 2 - fixedW > 0;
                var validRight = pos.left + pos.width / 2 + fixedW < pageW;
                var validTop = pos.top + pos.height / 2 - fixedH > 0;
                var validBottom = pos.top + pos.height / 2 + fixedH < pageH;


                switch (placement) {
                    case 'bottom':
                        position = {
                            top: pos.top + pos.height,
                            left: pos.left + pos.width / 2 - targetWidth / 2
                        };
                        break;
                    case 'top':
                        position = {
                            top: pos.top - targetHeight,
                            left: pos.left + pos.width / 2 - targetWidth / 2
                        };
                        break;
                    case 'left':
                        position = {
                            top: pos.top + pos.height / 2 - targetHeight / 2,
                            left: pos.left - targetWidth
                        };
                        break;
                    case 'right':
                        position = {
                            top: pos.top + pos.height / 2 - targetHeight / 2,
                            left: pos.left + pos.width
                        };
                        break;
                    case 'top-right':
                        position = {
                            top: pos.top - targetHeight,
                            left: validLeft ? pos.left - fixedW : padding
                        };
                        arrowOffset = {
                            left: validLeft ? Math.min(elementW, targetWidth) / 2 + fixedW : _offsetOut
                        };
                        break;
                    case 'top-left':
                        refix = validRight ? fixedW : -padding;
                        position = {
                            top: pos.top - targetHeight,
                            left: pos.left - targetWidth + pos.width + refix
                        };
                        arrowOffset = {
                            left: validRight ? targetWidth - Math.min(elementW, targetWidth) / 2 - fixedW : _offsetOut
                        };
                        break;
                    case 'bottom-right':
                        position = {
                            top: pos.top + pos.height,
                            left: validLeft ? pos.left - fixedW : padding
                        };
                        arrowOffset = {
                            left: validLeft ? Math.min(elementW, targetWidth) / 2 + fixedW : _offsetOut
                        };
                        break;
                    case 'bottom-left':
                        refix = validRight ? fixedW : -padding;
                        position = {
                            top: pos.top + pos.height,
                            left: pos.left - targetWidth + pos.width + refix
                        };
                        arrowOffset = {
                            left: validRight ? targetWidth - Math.min(elementW, targetWidth) / 2 - fixedW : _offsetOut
                        };
                        break;
                    case 'right-top':
                        refix = validBottom ? fixedH : -padding;
                        position = {
                            top: pos.top - targetHeight + pos.height + refix,
                            left: pos.left + pos.width
                        };
                        arrowOffset = {
                            top: validBottom ? targetHeight - Math.min(elementH, targetHeight) / 2 - fixedH : _offsetOut
                        };
                        break;
                    case 'right-bottom':
                        position = {
                            top: validTop ? pos.top - fixedH : padding,
                            left: pos.left + pos.width
                        };
                        arrowOffset = {
                            top: validTop ? Math.min(elementH, targetHeight) / 2 + fixedH : _offsetOut
                        };
                        break;
                    case 'left-top':
                        refix = validBottom ? fixedH : -padding;
                        position = {
                            top: pos.top - targetHeight + pos.height + refix,
                            left: pos.left - targetWidth
                        };
                        arrowOffset = {
                            top: validBottom ? targetHeight - Math.min(elementH, targetHeight) / 2 - fixedH : _offsetOut
                        };
                        break;
                    case 'left-bottom':
                        position = {
                            top: validTop ? pos.top - fixedH : padding,
                            left: pos.left - targetWidth
                        };
                        arrowOffset = {
                            top: validTop ? Math.min(elementH, targetHeight) / 2 + fixedH : _offsetOut
                        };
                        break;

                }
                position.top += this.getOffsetTop();
                position.left += this.getOffsetLeft();

                return {
                    position: position,
                    arrowOffset: arrowOffset
                };
            }
        };
        $.fn[pluginName] = function(options, args) {
            var results = [];
            var $result = this.each(function() {

                var webuiPopover = $.data(this, 'plugin_' + pluginName);
                if (!webuiPopover) {
                    if (!options) {
                        webuiPopover = new WebuiPopover(this, null);
                    } else if (typeof options === 'string') {
                        if (options !== 'destroy') {
                            if (!args) {
                                webuiPopover = new WebuiPopover(this, null);
                                results.push(webuiPopover[options]());
                            }
                        }
                    } else if (typeof options === 'object') {
                        webuiPopover = new WebuiPopover(this, options);
                    }
                    $.data(this, 'plugin_' + pluginName, webuiPopover);
                } else {
                    if (options === 'destroy') {
                        webuiPopover.destroy();
                    } else if (options === 'setTitle') {
                        webuiPopover.setTitle(args);
                    } else if (typeof options === 'string') {
                        results.push(webuiPopover[options]());
                    }
                }
            });
            return (results.length) ? results : $result;
        };

        //Global object exposes to window.
        var webuiPopovers = (function () {
            var _hideAll = function () {
                hideAllPop();
            };
            var _destroyChildren = function (parentSelector) {
                destroyChildren(parentSelector);
            }
            var _create = function (selector, options) {
                options = options || {};
                $(selector).webuiPopover(options);
            };
            var _isCreated = function (selector) {
                var created = true;
                $(selector).each(function (item) {
                    created = created && $(item).data('plugin_' + pluginName) !== undefined;
                });
                return created;
            };
            var _show = function (selector, options) {
                if (options) {
                    $(selector).webuiPopover(options).webuiPopover('show');
                } else {
                    $(selector).webuiPopover('show');
                }
            };
            var _hide = function (selector) {
                $(selector).webuiPopover('hide');
            };
            var _disableAll = function () {
                _hideAll();
                isEnabled = false;
            };
            var _enableAll = function () {
                isEnabled = true;
            };
            var _updateContentAsync = function (selector, url) {
                var pop = $(selector).data('plugin_' + pluginName);
                if (pop) {
                    var cache = pop.getCache();
                    var type = pop.options.type;
                    pop.options.cache = false;
                    pop.options.url = url;

                    if (pop._opened) {
                        pop._opened = false;
                        pop.show();
                    } else {
                        pop.options.type = 'async';
                        pop.setContentASync(pop.content);
                    }
                    pop.options.cache = cache;
                    pop.options.type = type;
                }
            };

            return {
                show: _show,
                hide: _hide,
                create: _create,
                isCreated: _isCreated,
                hideAll: _hideAll,
                disableAll: _disableAll,
                enableAll: _enableAll,
                updateContentAsync: _updateContentAsync,
                destroyChildren: _destroyChildren
            };
        })();
        window.WebuiPopovers = webuiPopovers;
    }));
})(window, document);;
/**
 * Returns the value of an E+ enum type/name.
 * @param {string} type - The enum type.
 * @param {string} name - The enum name within the enum type.
 * @returns {Number} Returns the value of the enum, or undefined if not found.
 */
function getEnumValue(type, name) {
    return typeof window.enum === "object" && window.enum[type] && window.enum[type][name];
}

function ajaxFileUpload(elementID, handler, action, filetype, secure, showAlert, additionalParams, postEvent, returnfunc, onError, callback) {
    var fileUploadResult = null;

    //starting setting some animation when the ajax starts and completes
    $("#" + elementID)
        .ajaxStart(function () {
            //chrome will reset the input to a default state when submitted. This has may confuse the user,
            //so we remove it.
            //on success, the page is reloaded.  just FYI
            $('#exampleFile').remove();
            $("#importFileName").css('visibility', 'hidden');
            $('#progressbar').css('visibility', '');
            $(this).show();
        })
        .ajaxComplete(function () {
            $('#progressbar').text(getRes("upload_complete") + '!');
            $(this).hide();
        });
    /*
        preparing ajax file upload
        url: the url of script file handling the uploaded files
                    fileElementId: the file type of input element id and it will be the index of  $_FILES Array()
        dataType: it support json, xml
        secureuri:use secure protocol
        success: call back function when the ajax complete
        error: callback function when the ajax failed

    */
    $.elementID = elementID;
    $.ajaxFileUpload({
        url: '/GetJSONData.aspx?builder=' + handler + '&action=' + action + '&filetype=' + filetype + additionalParams,
        secureuri: secure,
        fileElementId: $.elementID,
        dataType: 'json',
        success: function (data, status) {
            if (postEvent)
                $('body').trigger(postEvent, data);
            var result = null;
            fileUploadResult = data;
            if (data.length) {
                result = data[0];
            } else {
                result = data;
            }

            if (typeof (callback) == "function") {
                return callback(data);
            } else if (typeof (result.code) != 'undefined' && showAlert) {
                if (result.code == "ERROR") {
                    if (showAlert) {
                        alert(result.text);
                    }
                    if (onError != null) {
                        onError(result.text);
                    }
                } else {
                    var filename = result.text.replace(' uploaded', '');
                    if (returnfunc != null) {
                        returnfunc(filename, data, status);
                    }
                }
            } else {
                if (result.code == "ERROR") {
                    if (showAlert) {
                        alert(getRes("error_occurred"));
                    }
                    if (onError != null) {
                        onError(result.text);
                    }
                } else {
                    if (returnfunc != null) {
                        returnfunc(filename, data);
                    }
                }
            }
        },
        error: function (data, status, e) {
            alert(getRes("error_occurred") + ":" + e);
        }
    }
    );
    return fileUploadResult;
}

(function ($) {
    $.event.special.destroyed = {
        remove: function (o) {
            if (o.handler) {
                o.handler();
            }
        }
    }
})(jQuery);

function serializeObject(form) {
    var values = form.serializeArray(),
        json = {};

    $.map(values, function (n) {
        json[n['name']] = n['value'];
    });

    return json;
}

function serializeObjectFromQueryString(queryString) {
    var pojo = {};
    if (queryString.substring(0, 1) === "?") {
        queryString = queryString.slice(1);
    }
    var nvps = queryString.split("&");
    for (var i = 0; i < nvps.length; i++) {
        var nvp = nvps[i].split("=");
        pojo[nvp[0]] = nvp[1];
    }
    return pojo;
}

function is_iPad() {
    return navigator.userAgent.match(/iPad/i)
}

function getSelectedStoreIds(doUseLibraryColumns) {
    var storeIds;

    if (doUseLibraryColumns) {
        storeIds = Object.keys(window.allStores);
    } else {
        if ($(".storeOptionCheck.box_checked").length !== 0) {
            storeIds = $(".storeOptionCheck.box_checked").map(function () {
                return $(this).attr('storeId');
            }).get();
        }

        if ((!storeIds || !storeIds.length) && window.buyingStores) {
            storeIds = window.buyingStores.map(function (bs) {
                return bs.storeID;
            });
        }
    }

    return storeIds;
}

function assignCompSalesData(storeData) {
    _.assign(window.compsSalesData, storeData);
}

function loadCompAnalytics(options) {
    var skus = [];

    if (_.isString(options.skuList)) skus = options.skuList.split(',');
    else if (_.isArray(options.skuList)) skus = options.skuList;
    else return;

    if ($("#selectedAccountName").length > 0) {
        $(".compSelAccount").html(":&nbsp;" + $("#selectedAccountName").html());
    }

    var values = {
        skus: skus
    };

    if (options.selectedOrgId && options.selectedOrgId.length) {
        $.ajax({
            type: "POST",
            data: JSON.stringify(values),
            cache: false,
            url: "api/me/comps/accounts/" + options.selectedOrgId + "/" + options.gridType,
            contentType: "application/json"
        })
            .done(function (data) {
                var compGridCells = [].concat(
                    getCompGridCellsToUpdateForAggregates(data, options),
                    getCompGridCellsToUpdateForStores(data, options)
                );

                updateCompGridCells(compGridCells);
            });
    }
}

function getCompGridCellsToUpdateForAggregates(data, options) {
    var compRows = options.rowClass
        ? document.getElementsByClassName('compRow' + options.rowClass)
        : document.getElementsByClassName('comp-row');
    var compGridCells = [];

    for (var i = 0, rows = compRows.length; i < rows; i++) {
        var aggregateCompGridCells = getCompGridCellsToUpdate(data, compRows[i].dataset.rowid, compRows[i].dataset.compskuFor, options.doUseLibraryColumns);
        compGridCells = compGridCells.concat(aggregateCompGridCells);
    }

    return compGridCells;
}

function getCompGridCellsToUpdateForStores(data, options) {
    if (!options.rowClassIndividual) return [];

    var storeCompRows = document.getElementsByClassName(options.rowClassIndividual);
    var storeIds = getSelectedStoreIds(options.doUseLibraryColumns);
    var compGridCells = [];

    for (var i = 0, rows = storeCompRows.length; i < rows; i++) {
        var storeCompGridCells = getStoreCompGridCellsToUpdate(data, storeCompRows[i].dataset.rowid, storeCompRows[i].dataset.compskuFor, options.doUseLibraryColumns, storeIds)
        compGridCells = compGridCells.concat(storeCompGridCells);
    }

    return compGridCells;
}

function getCompGridCellsToUpdate(data, skuComp, sku, doUseLibraryColumns) {
    var compGridCells = [];

    if (skuComp in data) {
        var value = data[skuComp];
        if (doUseLibraryColumns) {
            value.lastCirculationDate = formatDotNetDateTimeUsingLocale(value.lastCirculationDate);
            value.lastReceivedDate = formatDotNetDateTimeUsingLocale(value.lastReceivedDate);

            compGridCells.push(
                getCompGridCellToUpdate('lfc', sku, skuComp, value.lfs),
                getCompGridCellToUpdate('lyc', sku, skuComp, value.lys),
                getCompGridCellToUpdate('ch', sku, skuComp, value.currentOnHand),
                getCompGridCellToUpdate('co', sku, skuComp, value.currentOnOrder),
                getCompGridCellToUpdate('lcd', sku, skuComp, value.lastCirculationDate),
                getCompGridCellToUpdate('lrd', sku, skuComp, value.lastReceivedDate)
            );
        } else {
            for (var i = 0; i < 3; i++) {
                compGridCells.push(
                    getCompGridCellToUpdate('m' + (i + 1), sku, skuComp, value.sales[i]),
                    getCompGridCellToUpdate('i' + (i + 1), sku, skuComp, value.avgOH[i])
                );
            }

            compGridCells.push(
                getCompGridCellToUpdate('lfs', sku, skuComp, value.lfs),
                getCompGridCellToUpdate('lys', sku, skuComp, value.lys),
                getCompGridCellToUpdate('oh', sku, skuComp, value.currentOnHand),
                getCompGridCellToUpdate('oo', sku, skuComp, value.currentOnOrder)
            );
        }
    } else {
        if (doUseLibraryColumns) {
            compGridCells.push(
                getCompGridCellToUpdate('lfc', sku, skuComp),
                getCompGridCellToUpdate('lyc', sku, skuComp),
                getCompGridCellToUpdate('ch', sku, skuComp),
                getCompGridCellToUpdate('co', sku, skuComp),
                getCompGridCellToUpdate('lcd', sku, skuComp),
                getCompGridCellToUpdate('lrd', sku, skuComp)
            )
        } else {
            for (var i = 0; i < 3; i++) {
                compGridCells.push(
                    getCompGridCellToUpdate('m' + (i + 1), sku, skuComp),
                    getCompGridCellToUpdate('i' + (i + 1), sku, skuComp)
                );
            }

            compGridCells.push(
                getCompGridCellToUpdate('lfs', sku, skuComp),
                getCompGridCellToUpdate('lys', sku, skuComp),
                getCompGridCellToUpdate('oh', sku, skuComp),
                getCompGridCellToUpdate('oo', sku, skuComp)
            );
        }
    }

    return compGridCells;
}

function getStoreCompGridCellsToUpdate(data, skuComp, sku, doUseLibraryColumns, storeIds) {
    var value = {
        sales: [0, 0, 0],
        avgOH: [0, 0, 0],
        lfs: 0,
        lys: 0,
        currentOnHand: 0,
        currentOnOrder: 0,
        lastCirculationDate: '',
        lastReceivedDate: ''
    };

    if (storeIds) {
        storeIds.forEach(function (storeId) {
            var key = storeId.toLowerCase() + '_' + skuComp;

            if (_.isNil(data[key])) return;

            for (var i = 0; i < 3; i++) {
                value.sales[i] += data[key].sales[i] || 0;
                value.avgOH[i] += data[key].avgOHDec[i] || 0;
            }

            value.lfs += data[key].lfs || 0;
            value.lys += data[key].lys || 0;

            value.currentOnHand += data[key].currentOnHand || 0;
            value.currentOnOrder += data[key].currentOnOrder || 0;

            value.lastCirculationDate = getMoreRecentDateString(data[key].lastCirculationDate, value.lastCirculationDate);
            value.lastReceivedDate = getMoreRecentDateString(data[key].lastReceivedDate, value.lastReceivedDate);
        });
    }

    for (var i = 0; i < 3; i++) {
        value.avgOH[i] = Math.round(value.avgOH[i]);
    }

    var compData = {};

    compData[skuComp] = value;

    return getCompGridCellsToUpdate(compData, skuComp, sku, doUseLibraryColumns);
}

function getCompGridCellToUpdate(prefix, sku, compSku, value) {
    return {
        element: document.getElementById(prefix + '_' + compSku + '_' + sku),
        value: value || '-'
    };
}

function updateCompGridCells(compGridCells) {
    compGridCells.forEach(function (compGridCell) {
        compGridCell.element.textContent = compGridCell.value;
    });
}

function bookmarkTitle(sku, catalogId) {
    var index = window.items.indexOf(sku);

    if (index < 0) {
        modalAlert(getRes("error_unexpected"));
        return;
    }

    var values = {
        source: 0,
        catalogId: catalogId,
        sequence: index + 1,
        sord: getSortOrd(),
        sdir: getSortDir()
    };

    $.getJSON("/GetJSONData.aspx?builder=BookmarkSequence", values, function (data) {
        if (data.text == "OK") {
            pageChange("#dashboard")
        }
        else {
            alert(data.text);
        }
    })
}

function adjustFilterHeaders(filterType) {
    var checkedTotals = $('input.' + filterType + '_SelectInput:checked').length;
    var filterTotals = $("#" + filterType + "_total").html() * 1;
    if (checkedTotals == filterTotals) {
        $("#" + filterType + "_out_of").html("");
    } else {
        $("#" + filterType + "_out_of").html(checkedTotals + " " + getRes("word_of"));
    }
}

function getResults(filteredResults, totalResults) {
    //var filteredResults = $('.wFil').length * 1;
    //var totalResults = $('#totalResults').html() * 1;
    if (filteredResults == totalResults) {
        $("#results").html("");
    } else {
        $("#results").html(filteredResults + " of ");
    }
    var selected = isAnalyticsRowsUsed() ? EdelweissAnalytics.selected : window.selected;
    if (selected > 0) {
        $("#results").html("<span style='font-weight:normal;'>(" + selected + " " + getRes("selected") + ")</span>&nbsp;" + $("#results").html());
    }
}

function reColorRows(common, class1, class2) {
    var rowClass = ""
    var i2 = 0
    $(common).each(function () {
        if (rowClass == class1) { rowClass = class2 } else { rowClass = class1 }
        $('#' + $(this).attr("id")).removeClass(class2)
        $('#' + $(this).attr("id")).removeClass(class1)
        $('#' + $(this).attr("id")).addClass(rowClass)
        i2 += 1
    });
}

function ToggleRightSidebar(sku) {
    $("#markupSlideArrow" + sku).hide();
    if ($('#rightSidebar' + sku).hasClass('sidebar-open')) {
        HideRightSidebar(sku);
    }
    else {
        ShowRightSidebar(sku);
    }
}

function ShowRightSidebar(sku) {
    $("#markupSlideArrowClosed" + sku).hide();
    $("#rightSidebarClosed" + sku).hide();
    $("#rightSidebar" + sku).show();
    $("#rightSidebar" + sku).animate({
        width: '+=70%'
    }, 500, function () {
        $("#markupSlideArrow" + sku).show();
        $('#rightSidebar' + sku).removeClass('sidebar-closed').addClass('sidebar-open');
    });
}

function HideRightSidebar(sku) {
    $("#closedPostIt" + sku).css("height", $("#openPostIt" + sku).css("height"));
    $("#openPostIt" + sku).css("height", "auto");
    $("#rightSidebar" + sku).animate({
        width: '-=70%'
    }, 500, function () {
        $("#rightSidebar" + sku).hide();
        $("#rightSidebarClosed" + sku).show();
        $("#markupSlideArrowClosed" + sku).show();
        $('#rightSidebar' + sku).removeClass('sidebar-open').addClass('sidebar-closed');
    });
}

function ShowFullMarkupNote(sku) {
    $("#markupNote" + sku).removeClass('partialMarkupNote');
    $("#toggleMarkupNotesDiv" + sku).removeClass("icon-arrow-circle-down").addClass("icon-arrow-circle-up")
    $("#toggleMarkupNotesDiv" + sku).attr("data-showFull", "1")
}

function ShowPartialMarkupNote(sku) {
    $("#markupNote" + sku).addClass('partialMarkupNote');
    $("#toggleMarkupNotesDiv" + sku).removeClass("icon-arrow-circle-up").addClass("icon-arrow-circle-down")
    $("#toggleMarkupNotesDiv" + sku).attr("data-showFull", "0")
}

function enableFancyInput(iControl) {
    $('#' + iControl).val($('#' + iControl).attr('defaultValue'));
    $('#' + iControl).focus(function () {
        if ($(this).val() == $(this).attr('defaultValue')) {
            $(this).val('');
            $(this).removeClass('input_placeholder');
        }
    });
    $('#' + iControl).blur(function () {
        if ($(this).val() == '') {
            $(this).val($(this).attr('defaultValue'));
            $(this).addClass('input_placeholder');
        }
    });
}

function getHashValue(key) {
    var matches = location.hash.match(new RegExp(key + '=([^&]*)'));
    return matches ? matches[1] : null;
}

// Returns true if the specified dashboard lane type is visible
function isDashboardVisible(dashType) {
    return $("#dashboard_" + dashType).is(":visible");
}

// Returns true if the specified list view is visible
function isListViewVisible(resultType) {
    return $("#innerContent_" + resultType).is(":visible");
}

function toggleMuchLove(sku, entityType) {
    if ($(".love_" + sku).hasClass("icon-heart")) {
        var rating = parseInt($('.myRating_' + sku).html(), 10);
        if (rating === 9) {
            clearAssessment(sku);
        }
        else {
            //Already Loved
            openTurboReview(sku);
        }
    } else {
        //Not Yet Loved
        saveMuchLove(sku, entityType)
    }
}

function saveMuchLove(sku, entityType) {
    var values = {
        idString: 9,
        valueString: 9,
        sku: sku,
        review: "",
        shareString: ""
    };
    $(".love_" + sku).removeClass("icon-heart-o")
    $(".love_" + sku).addClass("icon-heart")
    $(".love_" + sku).removeClass("muchLoveOpen")
    $(".love_" + sku).addClass("muchLoveFull")

    $.post("/getJSONData.aspx?builder=SaveSKUAssessment", values, function (data) {
        if (data.code === "SUCCESS") {
            if ($(".myRating_" + sku).length > 0) {
                $(".myRating_" + sku).html(9);
                $(".myRating_" + sku).removeClass("hidden");
                if (window.filters && window.filters[sku]) {
                    window.filters[sku][60] = "60_1"
                }
            }
            incrementQHeadCount(entityType, 1);
            if ($(".simpleRating_" + sku).length > 0) {
                $(".simpleRating_" + sku).show()
            }
            if ($("#dash_1").length > 0) {
                var resultType = $("#selectedResult_1").val();
                if (resultType == 104) {
                    refreshThumbnails(resultType, 1, 10)
                }
            }
        }
    }, "json");
}

function CheckIfMarkupNoteIsMultiline(line) {
    var brTag = "<br>";
    var brTagIndex = line.indexOf(brTag);

    if (brTagIndex != -1 && brTagIndex < line.trim().length) {
        return true;
    }
    else {
        return false;
    }
}

function ToggleMarkupNotes(param) {
    var $markupNoteDiv = $("#toggleMarkupNotesDiv" + param.data.sku);
    var $markupNote = $('#markupNote' + param.data.sku);
    var showFull = $markupNoteDiv.attr("data-showfull");
    var newShowFull = showFull === "1" ? "0" : "1";
    $markupNoteDiv.attr("data-showfull", newShowFull);

    if (newShowFull === "1") {
        $markupNote.removeClass('partialMarkupNote');
        $(this).removeClass("icon-arrow-circle-down").addClass("icon-arrow-circle-up");
        $(this).attr("title", param.data.hideMarkupNoteToolTip);
    }
    else {
        $markupNote.addClass('partialMarkupNote');
        $(this).removeClass("icon-arrow-circle-up").addClass("icon-arrow-circle-down");
        $(this).attr("title", param.data.showMarkupNoteToolTip);
    }
}

function AddMarkupNoteToggle(sku, markupNotePreference, showMarkupNoteToolTip, hideMarkupNoteToolTip) {
    if ($("#toggleMarkupNotesDiv" + sku).length == 0) {
        var arrowClass;
        if (markupNotePreference == '0') {
            arrowClass = 'icon-arrow-circle-down';
        }
        else {
            arrowClass = 'icon-arrow-circle-up';
        }
        $('#toggleMarkupNotesDiv' + sku).addClass(arrowClass);

        if (markupNotePreference == '0') {
            $("#toggleMarkupNotesDiv" + sku).attr("title", showMarkupNoteToolTip);
        }
        else {
            $("#toggleMarkupNotesDiv" + sku).attr("title", hideMarkupNoteToolTip);
        }

        return true;
    }

    return false;
}

function CreateParagraphsForNotes(sku, markupPreference) {
    if (!$("#markupNote" + sku).length) {
        var elementsToAdd = _.template('<div id="{{divId}}"></div>');
        $("#markupNoteContainer_" + sku).append(elementsToAdd({ divId: "markupNote" + sku }));
    }
}

function canSelectStores() {
    return !!$(".storeOptionCheck").length;
}

function toggleCheck(item, itemRow) {
    if ($("#check_" + item).hasClass("checkmark_checked")) {
        $("#check_" + item).removeClass("checkmark_checked");
        $("#num_" + item).removeClass("num_checked");
        window.rows[itemRow].selected = 0
        window.selected -= 1
        $(".itemAllCheck").removeClass("checkmark_checked");
    } else {
        $("#check_" + item).addClass("checkmark_checked");
        $("#num_" + item).addClass("num_checked")
        window.rows[itemRow].selected = 1
        window.selected += 1
    }
    window.getResults();
}

function toggleSelectAllItems() {
    if ($(".itemAllCheck").hasClass("checkmark_checked")) {
        unSelectAllItems();
    } else {
        selectAllItems();
    }
}

function selectAllItems() {
    //   $(".tlList").addClass("sel");
    window.selected = window.rows.length;
    for (var i = 0; i < window.rows.length; i++) {
        window.rows[i].selected = 1;
    }
    $(".itemCheck").addClass("checkmark_checked");
    $(".itemRowNumber").addClass("num_checked");
    $(".checkVisual").addClass("red-stripe");
    $(".itemAllCheck").addClass("checkmark_checked");
    window.getResults();
}
function selectItemsOnCurrentPage() {
    if (window.items && window.pages) {
        var itemsOnPage = 50;
        var currentPage = parseInt($("#currentPage").val(), 10);
        var lowIndex = currentPage * itemsOnPage - itemsOnPage;
        var highIndex = lowIndex + itemsOnPage;
        var currentlySelectedItems = window.getSelectedItems() || [];
        var selectedItems = _.uniq(currentlySelectedItems.concat(window.rows.slice(lowIndex, highIndex)));

        window.selected = selectedItems.length;
        for (var i = lowIndex; i < window.rows.length; i++) {
            for (var j = 0; j < selectedItems.length; j++) {
                if (window.rows[i].item === selectedItems[j].item) {
                    window.rows[i].selected = 1;
                    break;
                }
            }
        }
        $(".itemCheck").addClass("checkmark_checked");
        $(".itemRowNumber").addClass("num_checked");
        $(".checkVisual").addClass("red-stripe");
        $(".itemAllCheck").addClass("checkmark_checked");

        window.getResults();
    }
}

function unSelectAllItems() {
    window.selected = 0;
    for (var i = 0; i < window.rows.length; i++) {
        window.rows[i].selected = 0;
    }
    $(".itemCheck").removeClass("checkmark_checked");
    $(".itemRowNumber").removeClass("num_checked");
    $(".itemAllCheck").removeClass("checkmark_checked");
    window.getResults();
}

function populateMarkupTags(sku, tagList) {
    $("#quickTags_" + sku).tag({
        tagClassRoot: 'markupTag',
        allowEdit: true,
        tagAutoCompleteList: window.quickTagAutoComplete,
        tagList: tagList,
        onPreAddEventHandler: function (value, callback) {
            var success = false;
            var data = {
                skus: [sku],
                tags: [value]
            };

            $.ajax({
                type: 'POST',
                url: "api/me/api/markupTags/" + getListViewProperty("selectedMailingID"),
                dataType: "json",
                contentType: 'application/json; charset=utf-8',
                data: JSON.stringify(data)
            }).done(function (data) {
                success = true;
                window.UpdateQuickTagAutoCompletes(data, $("input[id^=quickTags]"));
                var tagValue = -1;
                $(".marktagFilters").each(function () {
                    var thisTag = htmlAttributeDecode($(this).attr('data-tag'));
                    if (thisTag == value) {
                        tagValue = htmlAttributeDecode($(this).attr('data-value'));
                    }
                });

                if (tagValue == -1) {
                    var cleaned = value.split(' ').join('_');
                    $.url = "/GetTreelineControl.aspx?controlName=/uc/filters/filterSelectRowTag.ascx&tag=" + cleaned + "&tagValue=" + tagValue + "&tagType=mark";
                    $("#marktagDiv").append('<div id="marktag_' + tagValue + '" class="menuOption filterRow_marktag"></div>');
                    $("#marktag_" + tagValue).load($.url, function () {
                        $("#marktag_" + tagValue).click(function () {
                            var controlId = $(this).attr('id');
                            changeFilter("marktag", controlId);
                        });
                    });
                }

                deferUpdateMarkupRefinements();

                callback(success);
            }).fail(function () {
                alert(getRes("error_unexpected"));
            });
        },
        onPreRemoveEventHandler: function (value, callback) {
            var success = false;
            var data = {
                sku: sku,
                tag: value
            };

            $.ajax({
                type: 'DELETE',
                url: "api/me/api/markupTags/" + getListViewProperty("selectedMailingID"),
                dataType: "json",
                contentType: 'application/json; charset=utf-8',
                data: JSON.stringify(data)
            }).done(function (data) {
                success = true;
                window.UpdateQuickTagAutoCompletes(data, $("input[id^=quickTags]"));

                var tagValue = -1;
                $(".marktagFilters").each(function (i, obj) {
                    var thisTag = htmlAttributeDecode($(this).attr('data-tag'));
                    if (thisTag == value) {
                        tagValue = htmlAttributeDecode($(this).attr('data-value'));
                    }
                });

                if ($("#lv_" + sku).hasClass("lv_marktag_" + tagValue)) {
                    $("#lv_" + sku).removeClass("lv_marktag_" + tagValue);
                }
                if ($(".lv_marktag_" + tagValue).length === 0) {
                    $("#marktag_" + tagValue).remove();
                }

                deferUpdateMarkupRefinements();

                callback(success);
            }).fail(function () {
                alert(getRes("error_unexpected"));
            });
        }
    });
}

function deferUpdateMarkupRefinements() {
    if (!window.markupThrottleTimeout) {
        window.markupThrottleTimeout = setTimeout(function () {
            populateMarkupTagRefine(44, 'Untagged Titles');
            window.markupThrottleTimeout = null;
        }, 100);
    }
}

function deferUpdateOrgRefinements() {
    if (!window.orgThrottleTimeout) {
        window.orgThrottleTimeout = setTimeout(function () {
            populateOrgTagRefine(45, 'Tags_Account', 'Untagged Titles');
            window.orgThrottleTimeout = null;
        }, 100);
    }
}

function upsertEventGridStatus(campaignID, campaignStatus) {
    $.post("/GetJSONData.aspx?builder=UpsertCampaignStatusForUser", { campaignID: campaignID, campaignStatus: campaignStatus },
        function (data) {
            if (data.code == "ERROR") {
                alert(data.text);
            }
        });
}

function upsertMultipleEventGridStatus(campaignIDList, campaignStatus) {
    $.post("/GetJSONData.aspx?builder=UpsertMultipeCampaignStatusForUser", { campaignIDList: campaignIDList.join(","), campaignStatus: campaignStatus },
        function (data) {
            if (data.code == "ERROR") {
                alert(data.text);
            }
        });
}

function updateEventGridsCountsOnPage(subtractTotalElem, addTotalElem) {
    if ($(subtractTotalElem) && $(addTotalElem)) {
        var subtractTotalValue = parseInt($(subtractTotalElem).html(), 10);
        var addTotalValue = parseInt($(addTotalElem).html(), 10);

        if (_.isNumber(subtractTotalValue)) {
            $(subtractTotalElem).html(subtractTotalValue - 1);
        }

        if (_.isNumber(addTotalValue)) {
            $(addTotalElem).html(addTotalValue + 1);
        }
    }
}

function submitEventGridRequestInListView(campaignID, campaignOrgID, requestDate, target, today, daysUntilDue, submitConfirmMessage, unsubmitConfirmMessage, submitText, unsubmitText, dueDateCSSClass, subtractTotalElem, addTotalElem, removeItem) {
    submitEventGridRequest(campaignID, campaignOrgID, requestDate, target, today, daysUntilDue, submitConfirmMessage, unsubmitConfirmMessage, submitText, unsubmitText, dueDateCSSClass);
    if (removeItem === 'True') {
        $('#as_' + campaignID).parent().remove();
        updateEventGridsCountsOnPage(subtractTotalElem, addTotalElem);
    }
}

function submitEventGridRequest(campaignID, campaignOrgID, requestDate, target, today, daysUntilDue, submitConfirmMessage, unsubmitConfirmMessage, submitText, unsubmitText, dueDateCSSClass) {
    if (confirm(submitConfirmMessage)) {
        jQuery.post("/GetJSONData.aspx?builder=SubmitCampaignRequest", { campaignID: campaignID, campaignOrgID: campaignOrgID },
            function (data) {
                if (data.code == "SUCCESS") {
                    requestDate.html(today)

                    target.off('click');
                    target.click(function () {
                        unsubmitEventGridRequest(campaignID, "'" + campaignOrgID + "'", requestDate, target, today, daysUntilDue, submitConfirmMessage, unsubmitConfirmMessage, submitText, unsubmitText, dueDateCSSClass);
                    });
                    target.val("Unsubmit");
                    target.text(unsubmitText);
                    target.removeClass("dueDateCSSClass");
                }
                else {
                    alert(data.text);
                }
            }, "json");
    }
}

function unsubmitEventGridRequestInListView(campaignID, campaignOrgID, requestDate, target, today, daysUntilDue, submitConfirmMessage, unsubmitConfirmMessage, submitText, unsubmitText, dueDateCSSClass, subtractTotalElem, addTotalElem, removeItem) {
    unsubmitEventGridRequest(campaignID, campaignOrgID, requestDate, target, today, daysUntilDue, submitConfirmMessage, unsubmitConfirmMessage, submitText, unsubmitText, dueDateCSSClass);
    if (removeItem === 'True') {
        $('#as_' + campaignID).parent().remove();
        updateEventGridsCountsOnPage(subtractTotalElem, addTotalElem);
    }
}
function unsubmitEventGridRequest(campaignID, campaignOrgID, requestDate, target, today, daysUntilDue, submitConfirmMessage, unsubmitConfirmMessage, submitText, unsubmitText, dueDateCSSClass) {
    if (confirm(unsubmitConfirmMessage)) {
        jQuery.post("/GetJSONData.aspx?builder=UnSubmitCampaignRequest", { campaignID: campaignID },
            function (data) {
                if (data.code == "SUCCESS") {
                    requestDate.html(daysUntilDue)
                    target.off('click');
                    target.click(function () {
                        submitEventGridRequest(campaignID, "'" + campaignOrgID + "'", requestDate, target, today, daysUntilDue, submitConfirmMessage, unsubmitConfirmMessage, submitText, unsubmitText, dueDateCSSClass);
                    });
                    target.val("Submit");
                    target.text(submitText);
                    target.addClass(dueDateCSSClass);
                }
                else {
                    alert(data.text);
                }
            }, "json");
    }
}

/* --- Ordering Control Functionality --- */
function enableOrderEditing(settings) {
    $(".catLocDiv_All").each(function () {
        var $self = $(this);
        var sku = $self.attr("data-sku");
        $self.webuiPopover({
            type: 'async',
            cache: false,
            container: '#pageContent',
            url: "/GetTreelineControl.aspx?controlName=/uc/controls/OrderingControlCatSelect.ascx&sku=" + sku + "&storeID=All",
            async: {
                success: function () {
                    window.ActivateLineItemDept(sku, 'All');
                }
            }
        });
    });

    $(".catLocDiv").each(function () {
        var $self = $(this);
        var sku = $self.attr("data-sku");
        var storeId = $self.attr("data-storeID");
        var lineItem = $self.data("lineitem");
        $self.webuiPopover({
            type: 'async',
            cache: false,
            url: "/GetTreelineControl.aspx?controlName=/uc/controls/OrderingControlCatSelect.ascx&sku=" + sku + "&storeID=" + storeId + (_.isNil(lineItem) ? "" : "&lineItem=" + lineItem),
            container: '#pageContent',
            async: {
                success: function () {
                    window.ActivateLineItemDept(sku, storeId, lineItem);
                }
            }
        });
    });

    $(".posDisplay").each(function () {
        var $self = $(this);
        var sku = $self.attr("data-sku");
        var storeID = $self.attr("data-storeID");
        $self.webuiPopover({ url: "#posDetail_" + storeID + "_" + sku, trigger: "hover", container: '#pageContent', delay: 300 });
    });

    $(".posDisplay_All").each(function () {
        var $self = $(this);
        var sku = $self.attr("data-sku");
        $self.webuiPopover({ url: "#posDetail_" + sku, trigger: "hover", container: '#pageContent', delay: 300 });
    });

    $(".exclame").each(function () {
        var $self = $(this);
        var sku = $self.attr("data-sku");
        $self.webuiPopover({ type: "async", url: "/GetTreelineControl.aspx?controlName=/uc/orders/OrderSKUSummary.ascx&sku=" + sku, trigger: "hover", container: '#pageContent', delay: 300 });
    });

    window.ePlus.modules.orders.lineItems.repDiscounts.ui.initialize(settings.lineItems.repDiscounts);
}

/* --- End Ordering Control Functionality --- */

/* --- Category Processing Functionality --- */
function closeCategories(sku, storeId, lineItem) {
    $(".catLocDiv_" + storeId + "_" + sku + (_.isNil(lineItem) ? "" : "_" + lineItem)).webuiPopover("hide");
}

function buildCategoryLocationSelector(storeId, sku, sequence, lineItem) {
    lineItem = _.isNil(lineItem) ? "" : lineItem + "_";
    return $("#inputCatLoc_" + storeId + "_" + sku + "_" + lineItem + sequence);
}
function buildCategory(elem) {
    var key = $(elem).attr("id").replace("inputCatLoc_", ""),
        category = {
            key: key,
            origValue: $("#catLoc_" + key).html(),
            newValue: $(elem).val()
        };

    category.hasChanged = category.origValue !== category.newValue;

    return category;
}

function checkCategoryEdit(textInputElem, event) {
    if (event && event.keyCode === 13) {
        event.stopPropagation();
        var $t = $(textInputElem);
        saveCategories($t.attr("data-sku"), $t.attr("data-store-id"), $t.attr("line-item"), true);
    }
    return false;
}

function loadAutoComplete(textInputElement) {
    var $element = $(textInputElement);
    var inputId = $element.attr("id");
    $element.val("");
    window.LoadDepartments(inputId, $element.attr("data-sku"), $element.attr("data-store-id"), $element.attr("line-item"));
}

function updatePrimaryCategory(sku, category, defaultCategoryMessage) {
    if (!category) return;

    var value = category.newValue,
        hasCategory = true,
        hasSuggestion = false;

    var mappedCategory = "";
    if (!value.length) {
        mappedCategory = $(".mappedCat" + sku).val();

        if (mappedCategory.length) {
            value = mappedCategory;
            hasSuggestion = true;
        } else {
            value = defaultCategoryMessage;
            hasCategory = false;
        }
    }

    $(".catLoc_" + category.key)
        .html(value)
        .css("font-weight", hasCategory ? "bold" : "normal")
        .attr("title", value);
    $(".catAuto_" + sku).toggle(hasSuggestion);

    if (hasSuggestion) {
        $(".inputCatLoc_" + category.key).val(mappedCategory);
    }
}
function updateCategories(sku, storeId, categories, defaultCategoryMessage) {
    if (!categories) return;

    $.each(categories, function (i, category) {
        if (storeId === "All" && i === 0) {
            updatePrimaryCategory(sku, category, defaultCategoryMessage);
        } else {
            $(".catLoc_" + category.key)
                .html(category.newValue)
                .attr("title", category.newValue);
            $(".catLocDisplay_" + category.key).toggle(category.newValue.length > 0);
        }
    });

}

function saveCategories(sku, storeId, lineItem, doClose, defaultCategoryMessage, errorMessage) {
    var hasChanges = false,
        categories = [],
        values = {
            storeID: storeId,
            sku: sku
        };

    if (!_.isNil(lineItem)) values.lineItem = lineItem;

    $(".inputCatLoc").each(function (i) {
        var category = buildCategory(this);

        values["category" + (i + 1)] = encodeURI(category.newValue);
        hasChanges = hasChanges || category.hasChanged;

        categories.push(category);
    });

    if (hasChanges) {
        var url;
        if (_.has(values, "lineItem")) {
            url = "/getJSONData.aspx?builder=SaveOrderLineItem";
            values.orderId = o.utils.getSelectedOrderId();
            values.units = o.utils.buildLineItemInputSelector(storeId, sku, lineItem).val() || "";
            values.price = window.prices[sku];
        } else {
            url = "/GetJSONData.aspx?builder=SaveOrganizationCategories";
        }

        $("#catLocInputBlock").show();

        $.getJSON(url, values, function (data) {
            $("#catLocInputBlock").hide();
            if (data && (data.code === "SUCCESS" || data.code === "OK")) {
                updateCategories(sku, storeId, categories, defaultCategoryMessage);
                o.updateCategories(sku, storeId, lineItem, decodeURI(values["category1"]), decodeURI(values["category2"]));
            } else {
                alert(errorMessage);
            }

            if (doClose) {
                closeCategories(sku, storeId, lineItem);
            }
        });
    } else if (doClose) {
        closeCategories(sku, storeId, lineItem);
    }
}

function removeCategory(sku, storeId, sequence, lineItem) {
    var $catInput = buildCategoryLocationSelector(storeId, sku, sequence, lineItem);
    $catInput.val("");
    saveCategories(sku, storeId, lineItem, false);
    $catInput.select().focus();
}

/* --- End Category Processing Functionality --- */

function hideFilter(filterType) {
    $("#refineFilter" + filterType).hide().addClass("done");
}

/**
 * Safely sets sort refine value, if the sort refine object exists
 * @param {number} index Index of the sort refine object in window.sortrefine
 * @param {string} propName Property to set
 * @param {Any} value
 * @returns {boolean} Result of value setting
 */
function setSortRefineProperty(index, propName, value) {
    if (window.sortrefine && window.sortrefine[index]) {
        window.sortrefine[index][propName] = value;
        return true;
    }

    return false;
}

//This populates the 'Account Shared Note' refine for Rep view via an API
function populateAccountNoteRefine(filterType) {
    if (getListViewProperty("selectedOrgID") != "") {
        var selectedOrgId = getListViewProperty("selectedOrgID");
        var postParams = JSON.stringify(window.items);
        if (selectedOrgId && selectedOrgId.length > 0 && window.items) {
            $.ajax({
                type: "POST",
                data: postParams,
                cache: false,
                url: "api/me/notes/accounts/" + selectedOrgId,
                contentType: "application/json",
                success: function (data) {
                    for (var i = 0; window.items && i < window.items.length; i++) {
                        if (window.items[i] in data) {
                            setSortRefineProperty(i, "AccountNote", 1);
                        } else {
                            setSortRefineProperty(i, "AccountNote", 0);
                        }
                    }
                    calculateSectionTotalsVariable(filterType);
                }
            });
        }
    } else {
        hideFilter(filterType);
    }
}

//This populates the 'Suggestion' refine for Rep view (for now) via an API
function populateSuggestionRefine(filterType) {
    if (getListViewProperty("selectedOrgID") != "") {
        var selectedOrgId = getListViewProperty("selectedOrgID");
        var postParams = JSON.stringify(window.items);
        if (selectedOrgId && selectedOrgId.length > 0 && window.items) {
            $.ajax({
                type: "POST",
                data: postParams,
                cache: false,
                url: "api/me/suggestions/accounts/" + selectedOrgId,
                contentType: "application/json",
                success: function (data) {
                    for (var i = 0; window.items && i < window.items.length; i++) {
                        var suggestions = null;

                        if (window.items[i] in data) {
                            var sugObject = data[window.items[i]];
                            for (var j = 0; j < sugObject.length; j++) {
                                if (sugObject[j].storeID != null) {
                                    if (suggestions === null) {
                                        suggestions = 0;
                                    }
                                    suggestions += +sugObject[j].units;
                                }
                            }
                        }

                        if (suggestions !== null) {
                            setSortRefineProperty(i, "Suggestions", 1);
                        } else {
                            setSortRefineProperty(i, "Suggestions", 0);
                        }
                    }
                    calculateSectionTotalsVariable(filterType);
                }
            });
        }
    } else {
        hideFilter(filterType);
    }
}

//This populates the 'Order' refine for Rep view (for now) via an API
function populateOrderRefine(filterType) {
    if (getListViewProperty("selectedOrgID") != "") {
        var selectedOrgId = getListViewProperty("selectedOrgID");
        var postParams = JSON.stringify(window.items);
        if (selectedOrgId && selectedOrgId.length > 0 && window.items) {
            $.ajax({
                type: "POST",
                data: postParams,
                cache: false,
                url: "api/me/orders/accounts/" + selectedOrgId + "/0",
                contentType: "application/json",
                success: function (data) {
                    for (var i = 0; window.items && i < window.items.length; i++) {
                        var orders = 0;
                        if (window.items[i] in data) {
                            var sugObject = data[window.items[i]];
                            for (var i2 = 0; i2 < sugObject.length; i2++) {
                                if (sugObject[i2].storeID != null && sugObject[i2].orgID == selectedOrgId) {
                                    orders += +sugObject[i2].units;
                                }
                            }
                        }
                        if (orders > 0) {
                            setSortRefineProperty(i, "Order", 1);
                        } else {
                            setSortRefineProperty(i, "Order", 0);
                        }
                    }
                    calculateSectionTotalsVariable(filterType);
                }
            });
        }
    } else {
        hideFilter(filterType);
    }
}

//This populates the 'Priority' refine for Rep view (for now) via an API
function populateMarkupPriorityRefine(filterType) {
    if (getListViewProperty("selectedMailingID") > 0) {
        var selectedMailingId = getListViewProperty("selectedMailingID");
        var postParams = JSON.stringify(window.items);
        if (window.items) {
            $.ajax({
                type: "POST",
                data: postParams,
                cache: false,
                url: "api/me/markupPriorities/accounts/" + selectedMailingId,
                contentType: "application/json",
                success: function (data) {
                    for (var i = 0; window.items && i < window.items.length; i++) {
                        if (window.items[i] in data) {
                            var priorityObject = data[window.items[i]];
                            setSortRefineProperty(i, "Priority_Markup", priorityObject);
                        } else {
                            setSortRefineProperty(i, "Priority_Markup", 0);
                        }
                    }
                    calculateSectionTotalsVariable(filterType);
                }
            });
        }
    } else {
        hideFilter(filterType);
    }
}

//This populates the 'Markup Notes' refine for Rep view (for now) via an API
function populateMarkupNoteRefine(filterType) {
    if (getListViewProperty("selectedMailingID") > 0) {
        var selectedMailingId = getListViewProperty("selectedMailingID");
        var postParams = JSON.stringify(window.items);
        if (window.items) {
            $.ajax({
                type: "POST",
                data: postParams,
                cache: false,
                url: "api/me/markupnotes/accounts/" + selectedMailingId + '?doIncludeMarkupNoteText=false',
                contentType: "application/json",
                success: function (data) {
                    for (var i = 0; window.items && i < window.items.length; i++) {
                        if (window.items[i] in data) {
                            setSortRefineProperty(i, "Note_Markup", 1);
                        } else {
                            setSortRefineProperty(i, "Note_Markup", 0);
                        }
                    }
                    calculateSectionTotalsVariable(filterType);
                }
            });
        }
    } else {
        hideFilter(filterType);
    }
}

//This populates the 'Markup Tag' refine for Rep view (for now) via an API
function populateMarkupTagRefine(filterType, unTaggedName) {
    if (getListViewProperty("selectedMailingID") > 0) {
        var selectedMailingId = getListViewProperty("selectedMailingID");
        var postParams = JSON.stringify(window.items);
        var masterTagList = [];
        if (window.items) {
            $.ajax({
                type: "POST",
                data: postParams,
                cache: false,
                url: "api/me/markuptags/accounts/" + selectedMailingId,
                contentType: "application/json",
                success: function (data) {
                    for (var i = 0; window.items && i < window.items.length; i++) {
                        var item = window.items[i];
                        var tempArray = [];

                        if (item in data) {
                            var tagObject = data[item];

                            for (var j = 0; j < tagObject.length; j++) {
                                var tagObjectValue = tagObject[j].value;

                                if (tagObjectValue != null) {
                                    tempArray.push(tagObjectValue);

                                    if (masterTagList.indexOf(tagObjectValue) == -1) {
                                        masterTagList.push(tagObjectValue);
                                    }
                                } else {
                                    tempArray.push(unTaggedName);
                                }
                            }
                        } else {
                            tempArray.push(unTaggedName);
                        }

                        setSortRefineProperty(i, "Tags_Markup", tempArray);
                    }

                    masterTagList.sort(function (a, b) {
                        return a.toLowerCase().localeCompare(b.toLowerCase());
                    });
                    masterTagList.unshift(unTaggedName);

                    populateRefineHTML(filterType, masterTagList);
                    window.refineMap[filterType] = masterTagList;
                }
            });
        }
    } else {
        hideFilter(filterType);
    }
}

//This populates the 'Org Tags' (mine, others, both) refine for Rep and Reader views via an API
function populateOrgTagRefine(filterType, filterName, unTaggedName) {
    var tagsCount = 0;
    var postParams = JSON.stringify(window.items);
    var masterTagList = [];
    if (window.items) {
        $.ajax({
            type: "POST",
            data: postParams,
            cache: false,
            url: "api/me/orgtags/" + filterName,
            contentType: "application/json",
            success: function (data) {
                masterTagList.push(unTaggedName);
                for (var i = 0; window.items && i < window.items.length; i++) {
                    var tempArray = []
                    if (window.items[i] in data) {
                        var tagObject = data[window.items[i]];
                        for (var j = 0; j < tagObject.length; j++) {
                            if (tagObject[j].value != null) {
                                tempArray.push(tagObject[j].value)
                                if (masterTagList.indexOf(tagObject[j].value) == -1) {
                                    masterTagList.push(tagObject[j].value);
                                }
                            }
                        }
                        tagsCount += 1
                    } else {
                        tempArray.push(unTaggedName);
                    }
                    setSortRefineProperty(i, filterName, tempArray);
                }

                masterTagList.sort(function (a, b) {
                    return a.toLowerCase().localeCompare(b.toLowerCase());
                });

                populateRefineHTML(filterType, masterTagList);
                window.refineMap[filterType] = masterTagList;

                $("#qhead41").html(tagsCount);
                showHide_qhead(41);
            }
        });
    }
}

//This populates the 'Markup Notes' shared with readers in the Refine section via an API
function populateSharedMarkupNoteRefine(filterType) {
    var postParams = JSON.stringify(window.items);
    if (window.items) {
        $.ajax({
            type: "POST",
            data: postParams,
            cache: false,
            url: "api/me/markupnotes/accounts/1",
            contentType: "application/json",
            success: function (data) {
                for (var i = 0; window.items && i < window.items.length; i++) {
                    if (window.items[i] in data) {
                        setSortRefineProperty(i, "Note_MarkupShared", 1);
                    } else {
                        setSortRefineProperty(i, "Note_MarkupShared", 0);
                    }
                }
                calculateSectionTotalsVariable(filterType);
            }
        });
    }
}

//This populates the 'On Hand' refine for Treeline locations (POS / ILS)
function populateAnalyticsRefine(filterType) {
    $.getJSON("/getJSONData.aspx?builder=GetPOSStockedSKUs", function (data) {
        if ("ERROR" == data[0]) {
            alert(data[0]);
        } else {
            for (var i = 0; i < window.sortrefine.length; i++) {
                if (data.indexOf(window.sortrefine[i].item) > 0) {
                    window.sortrefine[i].OwnedStock = 0;
                } else {
                    window.sortrefine[i].OwnedStock = 1;
                }
            }
            calculateSectionTotalsVariable(filterType);
        }
    });
}

function savePreferenceAndRedirectToLegacy(prefType, prefName) {
    savePreference(prefType, prefName, 1, redirectToLegacy);
}

function savePreferenceAndRedirectToLegacyAnalytics(prefType, prefName) {
    savePreference(prefType, prefName, 1, redirectToLegacyAnalytics);
}

function redirectToLegacy() {
    window.location.replace("https://edelweiss.abovethetreeline.com/");
}

function redirectToLegacyAnalytics() {
    window.open("http://edelweiss.abovethetreeline.com/sandbox/John/LegacyAnalytics/AnalyticsHome.aspx", "_blank");
}

function savePreference(prefType, prefName, value, preferenceSaveCallback) {
    var ajaxOptions = {
        dataType: "json",
        url: "/getJSONData.aspx?builder=SaveUserPreference",
        data: {
            type: prefType,
            name: prefName,
            value: value
        }
    };
    if (typeof preferenceSaveCallback !== "undefined" && _.isFunction(preferenceSaveCallback)) {
        ajaxOptions.success = preferenceSaveCallback;
    }
    $.ajax(ajaxOptions);
}

function savePreferenceWithCsrfToken(pref) {
    return $.ajax({
        type: 'POST',
        url: '/api/v2/me/preferences/' + pref.type + '/' + pref.name,
        contentType: 'application/json',
        data: JSON.stringify(pref.value),
        headers: ePlus.http.addAntiForgeryTokenToHeaders({}, pref.token)
    });
}

function saveTimePreference(resultType, daysBack, preferenceSaveCallback) {
    var ajaxOptions = {
        dataType: "json",
        url: "/getJSONData.aspx?builder=SaveUserTimePreference",
        data: {
            resultType: resultType,
            daysBack: daysBack
        }
    };
    if (typeof preferenceSaveCallback !== "undefined" && _.isFunction(preferenceSaveCallback)) {
        ajaxOptions.success = preferenceSaveCallback;
    }
    $.ajax(ajaxOptions);
}

function toggleSkip(sku, priority) {
    $("#as_" + sku).toggle();
    $(".mP_" + priority).toggleClass("tr_hideAbleRow");
}

function EditItem(orderID, row, sku, sortBy, sortDirection, protectedView, mode, referenceID) {
    $("body").trigger("orderAlert", [
        orderID,
        function () {
            var exportType = "";
            var reportname = "";

            if ($('#exportType').length) {
                exportInfo = $('#exportType').attr("val").split("~");
                exportType = exportInfo[0];
                reportname = exportInfo[1];
            }

            $.url = '/GetTreelineControl.aspx?controlName=/uc/product/EditOrderExportRecord.ascx&lineNumber=' + row;

            if (orderID) { $.url = $.url + "&orderID=" + orderID; }
            if (exportType) { $.url = $.url + "&exportType=" + exportType; }
            if (reportname) { $.url = $.url + "&reportname=" + encodeURIComponent(reportname); }
            if (sortBy) { $.url = $.url + "&sortBy=" + encodeURIComponent(sortBy); }
            if (sortDirection) { $.url = $.url + "&sortDirection=" + encodeURIComponent(sortDirection); }
            if (sku) { $.url = $.url + "&sku=" + encodeURIComponent(sku); }
            if (mode) { $.url = $.url + "&mode=" + encodeURIComponent(mode); }
            if (referenceID != null) { $.url = $.url + "&referenceID=" + referenceID }
            $.url = $.url + "&protected=" + protectedView;

            $.title = getRes("edit_export_record");

            if (row == -1) {
                $.title = getRes("add_export_record");
            }

            if (mode == 1)
                $.title = getRes("add_line_item");

            var buttonList = {};
            buttonList[getRes("save")] = function () {
                SaveDialog();
            };
            buttonList[getRes("close")] = function () {
                closeModal();
                if ($.orderDirty == true)
                    $('#exportContent').trigger("mappingUpdated");
            };
            closeModal();
            openModal($.url, "700px", "550px");
        }
    ]);
}

function formatDotNetDateTimeUsingLocale(data) {
    var dateDefault = "-";
    if (typeof data !== "undefined" && data !== null) {
        var timestamp = Date.parse(data);
        if (!isNaN(timestamp)) {
            var date = new Date(timestamp);
            return date.toLocaleString(window.cultureName,
                { year: "2-digit", month: "2-digit", day: "2-digit" });
        } else {
            return dateDefault;
        }
    } else {
        return dateDefault;
    }
}

function showHide_qhead(entityType) {
    if (+$("#qhead" + entityType).html() == 0) {
        $("#qhead" + entityType + "Container").hide();
    } else {
        $("#qhead" + entityType + "Container").show();
    }
}

function incrementQHeadCount(entityType, increment) {
    if ($("#qhead" + entityType).length > 0) {
        $("#qhead" + entityType).html($("#qhead" + entityType).html() * 1 + increment);
        showHide_qhead(entityType);
    }
}

function ShareCollectionInternally(catalogId, orgId) {
    var doShare = !$('#shareCol_' + catalogId).hasClass('sharedCollection');
    var url = "/api/v1/catalogs/" + catalogId + "/shares/" + doShare + "/organizations/" + orgId;

    $.ajax({
        type: "POST",
        url: url,
        success: function () {
            $('#shareCol_' + catalogId).toggleClass("sharedCollection");
        }
    });
}

function formatPagingSection() {
    var pages = +$(".pageOption").length;
    selectedPage = +$(".pagingSelected").attr("data-page");
    $(".pageEllipse").hide();
    if (pages < 8) {
        $(".pageOption").show();
    } else {
        $(".pageOption").hide();
        $("#pageOption1").show();
        $("#pageOption" + pages).show();
        if (selectedPage <= 4) {
            $("#pageOption2").show();
            $("#pageOption3").show();
            $("#pageOption4").show();
            $("#pageOption5").show();
            $("#pagePost5").show();
        } else if (selectedPage >= (pages - 3)) {
            $("#pageOption" + (pages - 1)).show();
            $("#pageOption" + (pages - 2)).show();
            $("#pageOption" + (pages - 3)).show();
            $("#pageOption" + (pages - 4)).show();
            $("#pagePre" + (pages - 4)).show();
        } else {
            $("#pagePre" + (selectedPage - 2)).show();
            $("#pageOption" + (selectedPage - 2)).show();
            $("#pageOption" + (selectedPage - 1)).show();
            $("#pageOption" + selectedPage).show();
            $("#pageOption" + (selectedPage + 1)).show();
            $("#pageOption" + (selectedPage + 2)).show();
            $("#pagePost" + (selectedPage + 2)).show();
        }
    }
}

function refreshDashboardCloud(resultType, loadingText) {
    savingModalOverlay(loadingText, "dashboardTagCloud");
    $.url = "/GetTreelineControl.aspx?controlName=/uc/dashboard/controls/Dashboard_TagCloud.ascx&TagType=" + resultType;
    $("#dashboardTagCloud").load($.url);
}

//This populates the Descriptions / Summaries for the visible titles
function populateProductDescriptions() {
    var postParams = JSON.stringify(window.items);
    if (window.items) {
        $.ajax({
            type: "POST",
            data: postParams,
            cache: false,
            url: "api/products/descriptions/summary",
            contentType: "application/json",
            success: function (data) {
                _.forEach(window.items, function (sku, i) {
                    if (_.has(data, sku)) {
                        $("#curSummary_" + window.items[i]).html(data[sku]);
                    }
                });
            }
        });
    }
}

function returnProductDescription(sku, populateTo) {
    var skuList = []
    skuList.push(sku)
    var postParams = JSON.stringify(skuList);
    if (window.items) {
        $.ajax({
            type: "POST",
            data: postParams,
            cache: false,
            url: "api/products/descriptions/summary",
            contentType: "application/json",
            success: function (data) {
                _.forEach(window.items, function (sku, i) {
                    if (_.has(data, sku)) {
                        $(populateTo).html(data[sku]);
                    }
                });
            }
        });
    }
}

function enableMoreMenu(sku) {
    var $moreIcons = sku ?
        $('#moreIcon_' + sku) :
        $('.moreIcon', '#itemContainer');

    enableActionMenu($moreIcons, '#moreMenu', 310, sku);
}

function enableShareMenu(sku) {
    var $shareIcons = sku ?
        $('#shareIcon_' + sku) :
        $('.shareIcon', '#itemContainer');

    enableActionMenu($shareIcons, '#shareMenu', 390, sku);
}

function enableActionMenu($elements, menuSelectorPrefix, menuWidth, sku) {
    $elements.each(function () {
        var $this = $(this),
            iconSku = $this.attr('data-sku'),
            url = menuSelectorPrefix + iconSku + (sku ? 'Modal' : ''),
            placement = sku ? 'top-left' : 'auto';

        var container = $("#as_" + iconSku).length ? "#as_" + iconSku : "#popModal_inner";

        $this.webuiPopover({
            url: url,
            width: menuWidth,
            placement: placement,
            container: container
        });
    });
}

function enablePriorityLink() {
    $('.mPriorityOption', $("#listContent")).each(function () {
        $this = $(this);
        var sku = $(this).attr("data-sku");
        $this.webuiPopover({
            type: "async",
            url: "/GetTreelineControl.aspx?controlName=/uc/controls/prioritySelector.ascx&sku=" + sku,
            trigger: "click",
            placement: "right",
            container: "#as_" + sku,
            delay: 300
        });
    });
}

function excelExportCampaign(campaignId, group) {
    var url;
    if (group == "eventgrid") {
        url = "/uc/PublicityCampaign/export/PublicityCampaignExport.aspx?campaignId=" + campaignId;
    } else {
        url = "/uc/PublicityCampaign/export/PublicityCampaignRequestsExport.aspx?campaignId=" + campaignId;
    }
    window.open(url, "export");
}

function showHideReview(row) {
    $("#more" + row).toggle();
    $("#reviewcontent" + row).toggle();
}

function getAutoCompleteForVisibleItems() {
    if (!window.autoCompleteJson || !window.sortrefine || !window.items) return [];

    return window.autoCompleteJson.filter(function (ac) {
        var i = window.items.indexOf(ac.value);
        return i > -1 && window.sortrefine[i] && !window.sortrefine[i].filteredOut;
    });
}

function initializeAutoComplete() {
    var autoComplete = getAutoCompleteForVisibleItems(),
        config = {
            max: 500,
            matchContains: true,
            mustMatch: true,
            width: 800,
            formatMatch: function (item) {
                return item.normalized;
            },
            formatItem: function (item) {
                return item.label;
            }
        };

    $("#autoComplete").unautocomplete().autocomplete(autoComplete, config).result(function (event, item) {
        $(this).val("");
        if (getListViewProperty("itemMode") == 0) {
            jumpToAndHighlightItem(item.value);
        } else {
            showSingleTitle(item.value);
        }
    });
}

function openTags(resultType, forceRefresh) {
    if ($("#dash_" + resultType).length > 0) {
        if ($("#extraRow" + resultType).css("display") == "none" || forceRefresh != null) {
            if ($("#extraRow" + resultType).html() == "" || forceRefresh != null) {
                $.url = "/GetTreelineControl.aspx?controlName=/uc/listviews/ListHomeOptions_Tags.ascx&resultType=" + resultType + "&dashType=" + getListViewProperty("dashboardType");
                $("#extraRow" + resultType).load($.url, function () {
                    $("#extraRow" + resultType).show();
                    $("#value_" + resultType).html($(".ltr_" + resultType).length);
                });
            } else {
                $("#extraRow" + resultType).show();
            }
        } else {
            $("#extraRow" + resultType).hide();
        }
    }
}

function toggleHomeOptions(row) {
    var dashType = getListViewProperty("dashboardType");
    //Add User Pref to Remember
    if ($(".homeOptionRow" + row).css("display") == "none") {
        $(".homeOptionRow" + row).slideDown();
        $(".homeOptionArrow" + row).removeClass("icon-drop-down-icon").addClass("icon-drop-up-icon-01");
        $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", { type: 'homeOption', name: 'section_' + row + '_' + dashType, value: 0 });
    } else {
        $(".homeOptionRow" + row).slideUp();
        $(".homeOptionArrow" + row).addClass("icon-drop-down-icon").removeClass("icon-drop-up-icon-01");
        $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", { type: 'homeOption', name: 'section_' + row + '_' + dashType, value: 1 });
    }
}

//This populates the peer activity variables and controls in the title list view
function populatePeerTitleActivity() {
    var postParams = JSON.stringify(window.items);
    if (window.items) {
        $.ajax({
            type: "POST",
            data: postParams,
            cache: false,
            url: "api/me/peerActivity",
            contentType: "application/json",
            success: function (data) {
                var filterTypesToUpdate = [];

                //User dictionary for # of Peer Stuff
                if (1 in data) {
                    window.peerActivity = [];
                    var userDictionary = data[1];
                    for (var key in userDictionary) {
                        var value = userDictionary[key];
                        window.peerActivity.push(key);
                    }
                    $("#qhead3").html(window.peerActivity.length);
                    $("#homeCom").show();
                } else {
                    $("#homeCom").hide();
                }

                //Reviews
                if (2 in data) {
                    var peerReviewFilterType = window.getEnumValue('filterType', 'PEERREVIEWS');
                    var reviewDictionary = data[2];
                    for (var i = 0; window.items && i < window.items.length; i++) {
                        var sku = window.items[i];
                        if (sku in reviewDictionary) {
                            var value = reviewDictionary[sku];
                            setSortRefineProperty(i, "PeerReviews", value);
                            setSortRefineProperty(i, "PeerReviewsRefine", 1);
                            if ($("#comReview_" + sku).length > 0) {
                                showActionPeerReviews(value, sku);
                            }
                        } else {
                            setSortRefineProperty(i, "PeerReviews", 0);
                            setSortRefineProperty(i, "PeerReviewsRefine", 0);
                            if ($("#comReview_" + sku).length > 0) {
                                hideActionPeerReviews(sku);
                            }
                        }
                    }

                    populateRefineLists(peerReviewFilterType);
                    filterTypesToUpdate.push(peerReviewFilterType);
                }

                //Shelves
                if (3 in data) {
                    var peerShelvesFilterType = window.getEnumValue('filterType', 'PEERSHELVES');
                    var shelfDictionary = data[3];
                    for (var i = 0; window.items && i < window.items.length; i++) {
                        var sku = window.items[i];
                        if (sku in shelfDictionary) {
                            var value = shelfDictionary[sku];
                            setSortRefineProperty(i, "PeerShelves", value);
                            setSortRefineProperty(i, "PeerShelvesRefine", value);
                            if ($("#myS_" + sku).length > 0) {
                                showActionPeerShelves(value, sku);
                            }
                        } else {
                            setSortRefineProperty(i, "PeerShelves", 0);
                            setSortRefineProperty(i, "PeerShelvesRefine", 0);
                            if ($("#myS_" + sku).length > 0) {
                                hideActionPeerShelves(sku);
                            }
                        }
                    }

                    populateRefineLists(peerShelvesFilterType);
                    filterTypesToUpdate.push(peerShelvesFilterType);
                }

                if (filterTypesToUpdate.length === 0) return;

                filterTypesToUpdate.forEach(calculateSectionTotalsVariable);
            }
        });
    }
}

function showActionPeerReviews(reviews, sku) {
    $("#comReview_" + sku).html(reviews).show();
    $("#myR_" + sku).show();
}

function hideActionPeerReviews(sku) {
    $("#myR_" + sku).hide();
}

function showActionPeerShelves(shelves, sku) {
    $("#comShelf_" + sku).html(shelves).show();
    $("#myS_" + sku).show();
}

function hideActionPeerShelves(sku) {
    $("#myS_" + sku).hide();
}

function getPeerUsersIntoString() {
    var result = "";
    for (var i = 0; i < window.peerActivity.length; i++) {
        if (result !== "") {
            result += ",";
        }
        result += window.peerActivity[i];
    }
    return result;
}

function adjustTimeBoxes(days) {
    $(".dayoption").addClass("box_unchecked").removeClass("box_checked");
    $(".day_" + days).addClass("box_checked");
}

function saveDashOptionTimeFrame(resultType, days, dashboardType, dashOption) {
    adjustTimeBoxes(days);
    saveTimePreference(resultType, days, function () {
        if (_.isNull(window.listView)) {
            //Adjust calendar icon and page control attributes and tooltip
            var selectedResultType = $("#selectedResult_" + dashboardType).val();
            var timeframeString = $("#timeString_" + days).html();
            $("#timeChange_" + resultType)
                .attr("data-days", days)
                .attr('title', timeframeString);

            $("#timeString_" + resultType).html(timeframeString);
            $("#timeframeString_" + selectedResultType).val(timeframeString);
            $("#timeframeDays_" + selectedResultType).val(days);

            updateDashboardHeader(dashboardType);
            $(".optRow" + dashOption).each(function (i, obj) {
                var resultType = $(this).attr("data-resultType");
                window.GetDashboardValue(resultType, 0);
                if (resultType === selectedResultType) {
                    if (dashboardType == window.dashType.tag) {
                        populateResultOptionsTags(resultType);
                    } else {
                        populateResultOptionsFull(resultType, dashboardType, $("#displayText_" + resultType).val(), 0);
                    }
                }
            });
        } else {
            reloadCurrentPage();
        }
        $('#timeChange_' + resultType).webuiPopover('hide');
    });
}

function toggleAdminProfilePreference(profileId) {
    $("#admin-profile-check-" + profileId).toggleClass("box_checked box_unchecked");
    $.ajax({
        dataType: "json",
        url: "/getJSONData.aspx?builder=ToggleAdminProfileVisibilty",
        data: {
            profileId: profileId
        }
    }).done(function (data) {
        if (data && data.code === "ERROR") {
            alert(data.text);
            $("#admin-profile-check-" + profileId).toggleClass("box_checked box_unchecked");
        } else if (ePlus.modules.drcAdminProfileSelectorPop) {
            ePlus.modules.drcAdminProfileSelectorPop.setUnsaved();
        }

    });
}

function updateEventGridStatus(campaignId, newStatus, dashType, subtractTotalElem, addTotalElem) {
    upsertEventGridStatus(campaignId, newStatus);
    $('#as_' + campaignId).parent().remove();
    updateEventGridsCountsOnPage(subtractTotalElem, addTotalElem);
}

function toggleSubList(refineType, row) {
    if (!$("#sub_" + refineType + "_" + row).length > 0 || $("#sub_" + refineType + "_" + row).css("display") == "none") {
        expandRefineSubLevels(refineType, row);
    } else {
        hideSubList(refineType, row);
    }
}
function showSubList(filterType, row) {
    var key = $("#" + filterType + "_" + row).attr("data-attr");
    var refinementTree = getRefinementTree(filterType);
    var refinementNode = _.find(refinementTree, { node: key });
    var emptyCheck = true;

    if (refinementNode) {
        var children = refinementNode.children.length;

        for (var i = 0; i < children; i++) {
            var secondLevelCategory = refinementNode.children[i].node;
            var secondLevelCategoryPart = secondLevelCategory.split(" / ")[1];

            var refinementElement = document.getElementById("fD_" + filterType + "_l_" + row + "_" + i);
            initializeRefinementElement(refinementElement, filterType, secondLevelCategoryPart);

            var $filterCheckbox = $("#f_" + filterType + "_l_" + row + "_" + i);
            ePlus.modules.leftNav.selectTempCategoryInLeftNavIfRefinementIsCurrentTempCategory(filterType,
                secondLevelCategoryPart, $filterCheckbox);

            $("#" + filterType + "_l_" + row + "_" + i).attr("data-attr", secondLevelCategory);

            if (refinementElement && refinementElement.innerHTML == "") {
                emptyCheck = false;
            }
        }
    }

    $("#sub_" + filterType + "_" + row).show();
    if (emptyCheck) {
        getRefineNumbers();
    }
    $("#sDrop" + filterType + "_" + row).removeClass("icon-drop-down-icon").addClass("icon-drop-up-icon-01");
}

function hideSubList(refineType, row) {
    $("#sub_" + refineType + "_" + row).remove();
    $("#sDrop" + refineType + "_" + row).addClass("icon-drop-down-icon").removeClass("icon-drop-up-icon-01");
}

function populateRefineLists(refineType) {
    if (refineType == 45) {
        // An account's tags, populated via API
        populateOrgTagRefine(45, 'Tags_Account', 'Untagged Titles');
    } else if (refineType == 44) {
        // A reps markup tags, populated via API
        populateMarkupTagRefine(44, 'Untagged Titles');
    } else if (window.refineMap && refineType in window.refineMap) {
        populateRefineHTML(refineType, window.refineMap[refineType]);
    }
}

function getRefinementTree(filterType) {
    return window.refinementTrees
        && window.refinementTrees[filterType]
        && window.refinementTrees[filterType].tree;
}

function doEncodeRefinement(filterType) {
    return filterType === getEnumValue('filterType', 'TAG');
}

function populateRefine_Simple(filterType, refinementList, startPosition) {
    for (var i = startPosition, max = refinementList.length; i < max; i++) {
        var refinement = refinementList[i];

        var refinementElement = document.getElementById("fD_" + filterType + "_" + i);
        initializeRefinementElement(refinementElement, filterType, refinement);

        var $filterCheckbox = $("#f_" + filterType + "_" + i);
        ePlus.modules.leftNav.selectTempCategoryInLeftNavIfRefinementIsCurrentTempCategory(filterType,
            refinement, $filterCheckbox);

        if (ePlus.modules.listView.refinements.isMultiValueRefinement(filterType)) {
            $("#" + filterType + "_" + i).attr("data-attr", refinement);
        } else {
            $("#" + filterType + "_" + i).attr("data-attr", i);
        }

        if (filterType == getEnumValue('filterType', 'CATEGORIES')) {
            var refinementTree = getRefinementTree(filterType);
            var refinementNode = _.find(refinementTree, { node: refinement });

            if (refinementNode && refinementNode.children && refinementNode.children.length > 1) {
                $("#sDrop" + filterType + "_" + i).show();
            }
        }
    }
    window.initializeFiltersVariable(filterType);
}

function initializeRefinementElement(element, filterType, value) {
    if (!element) return;

    var innerHtmlValue = doEncodeRefinement(parseInt(filterType)) ? htmlEncode(value) : value;
    var attributeValue = doEncodeRefinement(parseInt(filterType)) ? htmlAttributeEncode(value) : value;

    element.innerHTML = innerHtmlValue;
    element.setAttribute("data-key", attributeValue);
    element.setAttribute("title", attributeValue);
}

function expandRefineSubLevels(filterType, i) {
    $("#sub_" + filterType + "_" + i).remove();

    var key = $("#fD_" + filterType + "_" + i).attr("data-key");
    var refinementTree = getRefinementTree(filterType);
    var refinementNode = _.find(refinementTree, { node: key });

    if (refinementNode && refinementNode.children && refinementNode.children.length > 1) {
        var sublevels = refinementNode.children.length;
        url = '/GetTreelineControl.aspx?controlName=/uc/filters/filterSelectLevelTemplate.ascx&options=' + sublevels + '&filterType=' + filterType + "&parent=" + i;
        $("#" + filterType + "_" + i).append("<div id='sub_" + filterType + "_" + i + "' style='display:none;'></div>");
        $("#sub_" + filterType + "_" + i).load(url, function () {
            showSubList(filterType, i);
            window.initializeFiltersVariable(filterType);
        });
    }
}

function expandRefineHTML(filterType, doCheckBox) {
    var list = window.refineMap[filterType];
    var arrayLength = list.length;

    var refineDetail = {
        doCheckBox: doCheckBox,
        elementWidth: 125,
        filterOptionRows: _.range(5, arrayLength),
        filterType: filterType,
        isCategoryFilter: filterType == getEnumValue("filterType", "CATEGORIES"),
        doShowMore: false,
        showLessText: window.getRes('less')
    }
    $(".showMore_" + filterType, $("#refine" + filterType)).html(templateCache.getFilterSelectExpandTemplate(refineDetail));
    populateRefine_Simple(filterType, list, 5);
    if (doCheckBox) {
        buildFilterOptions(filterType);
    }
    getRefineNumbers();
    $(".showRefineLink_" + filterType, $("#refine" + filterType)).hide();
    $(".showMore_" + filterType, $("#refine" + filterType)).slideDown(function () {
        $(".hideRefineLink_" + filterType).show();
    });
}

function doShowAllRefinements(filterType) {
    return window.getListViewProperty("dashboardType") === getEnumValue("dashType", "LA_HOME")
        && filterType != getEnumValue("filterType", "POSCATEGORY");
}

function populateRefineHTML(filterType, list) {
    var arrayLength = list.length;
    var doShowMoreFilterTypes = [
        getEnumValue("filterType", "PRIORITY"), getEnumValue("filterType", "PRIORITYSHARED"),
        getEnumValue("filterType", "ALLACCOUNTTAG"), getEnumValue("filterType", "MARKUPTAG"),
        getEnumValue("filterType", "TAGSACCOUNTSHARED")
    ];

    var rowsToShow, doShowMore;
    if (doShowAllRefinements(filterType)) {
        rowsToShow = arrayLength;
        doShowMore = false;
    } else {
        rowsToShow = Math.min(5, arrayLength);
        if (doShowMoreFilterTypes.indexOf(+filterType) > -1) {
            rowsToShow = arrayLength;
        }
        doShowMore = rowsToShow >= 5 && doShowMoreFilterTypes.indexOf(+filterType) === -1;
    }

    var refineDetail = {
        doCheckBox: false,
        elementWidth: 125,
        filterOptionRows: _.range(0, rowsToShow),
        filterType: filterType,
        isCategoryFilter: filterType == getEnumValue("filterType", "CATEGORIES"),
        doShowMore: doShowMore,
        showMoreText: window.getRes('more')
    }
    $("#refine" + filterType).html(templateCache.getFilterSelectTemplate(refineDetail));
    populateRefine_Simple(filterType, list, 0);
    if (!isRefineHidden(filterType)) {
        $("#refine" + filterType).slideDown();
    }

    if (filterType == 137) {
        populateSharedMarkupNoteRefine(filterType);
    } else if (filterType == 136) {
        populateMarkupNoteRefine(filterType);
    } else if (filterType == 110) {
        populateMarkupPriorityRefine(filterType);
    } else if (filterType == 100) {
        populateAnalyticsRefine(filterType);
    } else if (filterType == 138) {
        // A reps account notes that have been shared, populated via API
        populateAccountNoteRefine(filterType);
    } else if (filterType == 81) {
        // A reps suggestions, populated via API
        populateSuggestionRefine(filterType);
    } else if (filterType == 90) {
        // A reps orders, populated via API
        populateOrderRefine(filterType);
    } else {
        calculateSectionTotalsVariable(filterType);
    }
}

function populateCombinedRefineHTML(combinedFilterType, filterTypes) {
    for (var i = 0; i < filterTypes.length; i++) {
        var filterType = filterTypes[i];
        var arrayLength = window.combinedRefineMap[filterType].length;
        var doShowMoreFilterTypes = [
            getEnumValue("filterType", "PRIORITY"), getEnumValue("filterType", "PRIORITYSHARED"),
            getEnumValue("filterType", "ALLACCOUNTTAG"), getEnumValue("filterType", "MARKUPTAG"),
            getEnumValue("filterType", "TAGSACCOUNTSHARED")
        ];
        var rowsToShow = Math.min(5, arrayLength);
        if (doShowMoreFilterTypes.indexOf(+filterType) > -1) {
            rowsToShow = arrayLength;
        }
        var doShowMore = rowsToShow >= 5 && doShowMoreFilterTypes.indexOf(+filterType) === -1;
        var refineDetail = {
            doCheckBox: false,
            elementWidth: 125,
            filterOptionRows: _.range(0, rowsToShow),
            filterType: filterType,
            isCategoryFilter: filterType == getEnumValue("filterType", "CATEGORIES"),
            doShowMore: doShowMore,
            showMoreText: window.getRes('more')
        }
        $("#refine" + filterType).html(templateCache.getFilterSelectTemplate(refineDetail));
        populateRefine_Simple(filterType, window.combinedRefineMap[filterType], 0);
        if (!isRefineHidden(filterType)) {
            $("#refine" + filterType).slideDown();
        }
    }

    if (combinedFilterType == 12) {
        ePlus.modules.listView.getAccountDetails(filterTypes);
    }
}

$(".refineHeader").click(function () {
    toggleRefineHeader();
});

function toggleRefineHeader(filterType) {
    if ($("#refine" + filterType).css("display") === "none") {
        openRefineHeaderOptions(filterType);
    } else {
        closeRefineHeaderOptions(filterType);
    }
}

function closeRefineHeaderOptions(filterType) {
    $("#refine" + filterType).slideUp();
    $("#homeOptionArrow" + filterType).removeClass("icon-drop-up-icon-01 ").addClass("icon-drop-down-icon");
}

function openRefineHeaderOptions(filterType) {
    if ($("#refine" + filterType).html() === "") {
        populateRefineLists(filterType);
        getRefineNumbers();
    } else {
        $("#refine" + filterType).slideDown();
        $("#homeOptionArrow" + filterType).removeClass("icon-drop-down-icon").addClass("icon-drop-up-icon-01 ");
    }
}

function populateRefineDetail() {
    var $refinements = $(".homeOptionRefineArea");

    if ($refinements.length === 0) {
        toggleNoRefinementsMessage();
        return;
    }

    $refinements
        .removeClass("done")
        .show()
        .each(function () {
            var filterType = $(this).attr("data-filterType");

            if (filterType && filterType != getEnumValue("filterType", "ACCOUNTCOMBINEDFILTER")) {
                populateRefineLists(filterType);
            } else if (filterType) {
                populateCombinedRefineHTML(filterType, window.refineMap[filterType]);
            }
        });

    for (var filterType in window.combinedRefineMap) {
        window.refineMap[filterType] = window.combinedRefineMap[filterType];
    }
}

function showMoreRefinements(filterType, doShowCheckbox) {
    if ($(".showMore_" + filterType).html() == "") {
        expandRefineHTML(filterType, doShowCheckbox);
    } else {
        $(".showRefineLink_" + filterType).hide();
        $(".showMore_" + filterType).slideDown(function () {
            $(".hideRefineLink_" + filterType).show();
        });
    }
}

function hideMoreRefinements(filterType) {
    $(".hideRefineLink_" + filterType).hide();
    $(".showMore_" + filterType).slideUp(function () {
        $(".showRefineLink_" + filterType).show();
    });
}

function toggleAllIllustrations(doShow) {
    if (doShow) {
        $(".illustrations-container").show();
    } else {
        $(".illustrations-container").hide();
    }
}

// Showcase Functions

function showShowcase(id, callback) {
    var $showcase = $('#' + id);

    function executeCallback() {
        if (typeof callback === 'function') {
            callback($showcase.get(0));
        }
    }

    if ($showcase.is(':hidden')) {
        $('#' + id + '-arrow')
            .removeClass('icon-drop-down-icon')
            .addClass('icon-drop-up-icon-01');

        var catalogId = $showcase.data('catalogid'),
            sku = $showcase.attr('data-sku'),
            url = '/GetTreelineControl.aspx?controlName=/uc/product/Showcase.ascx&catalogId=' + catalogId + '&sku=' + sku;

        $showcase.load(url, executeCallback).show();
    } else {
        executeCallback();
    }
}

function hideShowcase(id) {
    var $showcase = $('#' + id);

    if (!$showcase.is(':hidden')) {
        $('#' + id + '-arrow')
            .addClass('icon-drop-down-icon')
            .removeClass('icon-drop-up-icon-01');
        $showcase.hide();
    }
}

function toggleShowcase(elem) {
    var showcaseId = $(elem).data('showcaseid');

    if ($('#' + showcaseId).is(':hidden')) {
        showShowcase(showcaseId);
    } else {
        hideShowcase(showcaseId);
    }
}

function toggleAllShowcases(doShow) {
    $('.showcaseToggle').each(function () {
        var id = $(this).data('showcaseid');
        doShow ? showShowcase(id) : hideShowcase(id);
    });
}

function getShowcaseId(sku) {
    return ['showcase', '1', sku].join('_');
}

function getCatalogShowcaseId(collectionId) {
    return ['catalogShowcase', collectionId].join("_");
}

function jumpToCatalogShowcase(sku, collectionId) {
    jumpToItem(sku, function () {
        var showcaseId = getShowcaseId(sku);

        showShowcase(showcaseId, function (showcase) {
            var catalogShowcaseId = getCatalogShowcaseId(collectionId),
                $catalogShowcase = $('.' + catalogShowcaseId, showcase).first();

            if ($catalogShowcase.length) {
                var height = $('#listHeader').outerHeight(),
                    position = $catalogShowcase.offset(),
                    y = position.top - height;

                $(window).scrollTop(y);
            }
        });
    });
}

function onPopStateEvent(e) {
    if (!e || !e.originalEvent || !e.originalEvent.state) return;

    switch (e.originalEvent.state.source) {
        case 'welcome':
            handleWelcomePopStateEvent();
            break;
    }
}

function handleWelcomePopStateEvent() {
    abortAjaxRequests();
    $('#mainBlockDiv').hide();
    $('#pageBlockDiv').hide();
    $('.inProcessDiv', '#pageContent').remove();
}

window.doDisableNextHashChangeEvent = false;

function onHashChangeEvent() {
    if (!window.doDisableNextHashChangeEvent && location.hash.length > 0) {
        window.changePage(location.hash, location.hashCallback);
    } else if (!window.doDisableNextHashChangeEvent) {
        window.goToWelcomePage();
    } else {
        window.doDisableNextHashChangeEvent = false;
    }
}

function getPagePos(page, itemsPerPage) {
    page = _.parseInt(page, 10);
    return _.isNaN(page) ? 1 : itemsPerPage * (page - 1) + 1;
}

function getPageNum(startPos, itemsPerPage) {
    startPos = _.parseInt(startPos, 10);
    return _.isNaN(startPos) ? 1 : Math.floor(startPos / itemsPerPage) + 1;
}

function updatePageHash(startPos, itemsPerPage, hasBookmark) {
    var newHash = window.location.hash.replace(/&page=-?[0-9]+/, "");
    if (hasBookmark) {
        newHash = newHash.replace(/&(start|sord|sdir|bookmark)=-?[0-9]+/g, "");
    }
    history.replaceState(null, window.location.hash, newHash + "&page=" + getPageNum(startPos, itemsPerPage));
}

function isValidEmail(email) {
    var re = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
    return re.test(email);
}

/* --- Begin Suggestions --- */
function updateOrderSuggestionFilters(sku, filter) {
    var filterType = filter,
        item = $("#lv_" + sku),
        storeIds = [],
        storeUnits = {
            total: 0
        };

    $(".orderInput_" + sku).each(function () {
        var self = $(this),
            value = self.val(),
            storeId = self.data("storeid");

        storeIds.push(storeId);

        if ($.isNumeric(value)) {
            value = value * 1;
            storeUnits[storeId] = value;
            storeUnits.total += value;
        }
    });

    updateItemProductOrderClasses(item, filterType, storeIds, storeUnits);
}

function updateItemProductOrderClasses(item, filterType, storeIds, storeUnits) {
    var classNamesToRemove = [],
        classNamesToAdd = [];

    classNamesToRemove.push(buildFilterClassName(filterType, 0));
    classNamesToRemove.push(buildFilterClassName(filterType, 1));

    $.each(storeIds, function (i, storeId) {
        classNamesToRemove.push(buildFilterClassName(filterType, [0, storeId].join("_")));
        classNamesToRemove.push(buildFilterClassName(filterType, [1, storeId].join("_")));

        var filterValue = [storeUnits && storeUnits[storeId] ? 1 : 0, storeId].join("_");

        classNamesToAdd.push(buildFilterClassName(filterType, filterValue));
    });

    classNamesToAdd.push(buildFilterClassName(filterType, storeUnits.total ? 1 : 0));

    item.removeClass(classNamesToRemove.join(" ")).addClass(classNamesToAdd.join(" "));
}

function buildFilterClassName(filterType, value) {
    return ["lv", filterType, value].join("_");
}

function acceptSuggestion(storeId, sku, errorMessage, oType) {
    if (o.utils.buildInputSelector(storeId, sku).prop("disabled") || o.savingLock) return;
    var selectedStores = o.utils.getSelectedStoreIds();
    var selectedOrder = o.utils.getSelectedOrderId();
    var suggestion;
    if (selectedOrder > 0) {
        if (oType === "total" && selectedStores === "1" && selectedOrder > 0) {
            suggestion = $("#suggestion_" + sku).html();
            var input = $("#inputOrders_" + sku);
            input.val(suggestion);
            o.saveUnits(input, sku);
        } else {
            if (oType != "total") {
                suggestion = $("#suggestion_" + storeId + "_" + sku).html();
                var input = $("#inputOrders_" + storeId + "_" + sku);
                input.val(suggestion);
                o.saveUnits(input, sku);
            }
        }
    } else {
        modalAlert(errorMessage);
    }
}

/* --- End Suggestions --- */

function toggleStoreLineItems(elem, sku, storeId) {
    var $e = $(elem);
    $e.toggleClass("icon-drop-up-icon-01 icon-drop-down-icon");
    $("#lineItems_" + sku + "_" + storeId).slideToggle("fast");
}

function showVideos(videosId) {
    var $videos = $('#' + videosId);

    if ($videos.is(':hidden')) {
        $('#' + videosId + '-arrow')
            .removeClass('icon-drop-down-icon')
            .addClass('icon-drop-up-icon-01');

        var sku = $videos.attr('data-sku'),
            url = '/GetTreelineControl.aspx?controlName=/uc/product/ProductVideos.ascx&sku=' + sku;

        $videos.load(url).show();
    }
}

function hideVideos(videosId) {
    var $videos = $('#' + videosId);

    if (!$videos.is(':hidden')) {
        $('#' + videosId + '-arrow')
            .addClass('icon-drop-down-icon')
            .removeClass('icon-drop-up-icon-01');
        $videos.hide();
    }
}

function toggleVideos(videosId) {
    if ($('#' + videosId).is(':hidden')) {
        showVideos(videosId);
    } else {
        hideVideos(videosId);
    }
}

function clearUserPreference(prefType, prefName) {
    $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", { type: prefType, name: prefName, value: "" });
}

function createMarkupPopover(catalogID, displayText) {
    $("#sharedMarkups_" + catalogID).webuiPopover({
        title: displayText,
        type: "async",
        cache: false,
        container: "#pageContent",
        url: "/GetTreelineControl.aspx?controlName=/uc/catalog/CatalogSharedMarkups.ascx&catalogId=" + catalogID + "&resultType=" + getListViewProperty("resultType"),
        placement: "auto-top"
    });
}

function showMoreTags(resultType, tagSource) {
    var elementSuffix = resultType + "_" + tagSource;

    $(".showTagLink_" + elementSuffix).hide();
    $(".hideTagLink_" + elementSuffix).show();
    $(".moTag_" + elementSuffix).show();
}

function hideMoreTags(resultType, tagSource) {
    var elementSuffix = resultType + "_" + tagSource;

    $(".showTagLink_" + elementSuffix).show();
    $(".hideTagLink_" + elementSuffix).hide();
    $(".moTag_" + elementSuffix).hide();
}

function populateResultOptionsTags(resultType) {
    $("#selectedResult_" + window.dashType.tag).val(resultType);

    $("#resultName_" + window.dashType.tag).html($("#displayText_" + resultType).val());
    if ($("#timeString_" + resultType).length) {
        $("#resultName_" + window.dashType.tag).html($("#resultName_" + window.dashType.tag).html() + " " + $("#timeString_" + resultType).html());
    }

    if ($("#dash_" + resultType).length > 0) {
        $(".dashSection").css("background-color", "");
        $("#dash_" + resultType).css("background-color", "#e9ebec");
    }

    //Reload Tag Cloud
    $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", { type: 'tag', name: 'selectedTag', value: "" },
        function () {
            refreshDashboardCloud(resultType, window.loadingText);
        });

    //Empty Title Div
    $("#dashTitles_" + window.dashType.tag).html("");
    closeModal();
}

function goToListFromDashboard(dashboardType) {
    if (dashboardType === getEnumValue("dashType", "LA_HOME")
        || dashboardType === getEnumValue("dashType", "BA_HOME")
        || dashboardType === getEnumValue("dashType", "KPI_HOME")) {
        goToAnalyticsHome(dashboardType, 0);
    } else {
        var pageHash = "#dashList=" + dashboardType;
        window.sessionStorage.removeItem("folderBreadcrumbs");
        history.pushState(null, "", pageHash);
        window.dispatchEvent(new HashChangeEvent("hashchange"))
        if (dashboardType !== getListViewProperty("dashboardType")) {
            window.stopLoading = getListViewProperty("resultType");
        } else {
            window.stopLoading = null;
        }

        reloadCurrentPage();
    }
}

function goToResultFromDashboard(resultType, dashboardType) {
    var pageHash = "#dashList=" + dashboardType + "&rT=" + resultType;

    if (resultType === getEnumValue("resultType", 'CATALOG_HISTORYMONTHLISTS')) {
        pageHash = window.applicationConfigByKey['HistoryAwarenessWidgetUrl'];
    }

    history.pushState(null, "", pageHash);
    if (dashboardType !== getListViewProperty("dashboardType")) {
        window.stopLoading = getListViewProperty("resultType");
    } else {
        window.stopLoading = null;
    }

    reloadCurrentPage();
}

function selectTagFromDashboard(tag, count) {
    var resultType = $("#selectedResult_" + window.dashType.tag).val();
    window.selectedTag = encodeURIComponent(tag);
    window.selectedTagCount = count;
    highlightSelected(count);
    window.populateTitles_TagVariant(resultType, window.dashType.tag, tag);
}

function goToTagFromDashboard(tag, count) {
    var resultType = $("#selectedResult_" + window.dashType.tag).val();
    window.selectedTag = encodeURIComponent(tag);
    window.selectedTagCount = count;
    goToListFromDashboard(window.dashType.tag);
}

function enableShelfLink(position) {
    position = position || "bottom";

    var $constraint = $("#titleModalMain").length ? $("#popModal_inner") : $("#itemContainer");

    $('.uShelf', $constraint).each(function () {
        var $this = $(this);
        var sku = $this.attr("data-sku");
        var container = $("#titleModalMain").length ? "#popModal_inner" : "#as_" + sku;

        if ($(container).length === 0) {
            container = $("#titleModalMain").length ? "#popModal_inner" : "#as_" + $this.attr("data-item");
        }

        $this.webuiPopover({
            type: "async",
            cache: false,
            url: "/GetTreelineControl.aspx?controlName=/uc/controls/shelfSelector.ascx&sku=" + sku,
            trigger: "click",
            container: container,
            placement: position,
            backdrop: true,
            delay: 300
        });
    });
}

function enableCollectionLink() {
    $('.uCollection').each(function () {
        var $this = $(this);
        var sku = $this.attr('data-sku');
        var itemContainerId = $this.attr('data-item-container-id');
        var container = $('#titleModalMain').length ? '#popModal_inner' : '#as_' + itemContainerId;

        $this.webuiPopover({
            type: 'async',
            cache: false,
            url: '/GetTreelineControl.aspx?controlName=/uc/catalog/collectionManagement.ascx&sku=' + sku,
            trigger: "click",
            container: container,
            multi: true,
            placement: 'left',
            width: '600px',
            backdrop: true,
            delay: 300
        });
    });
}

function enableCatalogShareLinks() {
    $('.catalog-share-icon', $('#itemContainer')).each(function () {
        var $this = $(this);
        var catalogId = $this.attr('data-catalog-id');

        $this.webuiPopover({
            url: '#catalog-share-menu-' + catalogId,
            width: 390,
            placement: 'top-left'
        })
    });
}

function enableShareLinks() {
    $('.shareIcon', $("#itemContainer")).each(function () {
        $this = $(this);
        var sku = $(this).attr("data-sku");
        var $shareIcons = sku ?
            $('#shareIcon_' + sku) :
            $('.shareIcon', '#itemContainer');
        $this.webuiPopover({
            url: '#shareMenu' + sku,
            width: 390,
            placement: sku ? 'top-left' : 'auto'
        });
    });
}

function changeShelf(shelf, sku) {
    var oldShelf = window.selectedShelf;
    var newShelfIcon = $("#shelf_" + shelf).attr("data-shelfIcon");
    var oldShelfIcon = $("#shelf_" + oldShelf).attr("data-shelfIcon");
    $('.uShelf').webuiPopover('hide');
    $.getJSON("/GetJSONData.aspx?builder=SetShelfStatus", { sku: sku, status: shelf }, function (data) {
        if (data.code == "OK") {
            var $myShelf = $(".myShelf_" + sku);
            $myShelf.removeClass(oldShelfIcon)
                .addClass(newShelfIcon);
            $(".uShelf" + sku).attr("data-shelf", shelf);

            if (oldShelf <= 0) {
                $myShelf.removeClass("hidden");
                if ($(".simpleShelf_" + sku).length > 0) {
                    $(".simpleShelf_" + sku).show();
                }
            }
            if (shelf <= 0) {
                $myShelf.addClass("hidden");
                if ($(".simpleShelf_" + sku).length > 0) {
                    $(".simpleShelf_" + sku).hide();
                }
            }

            window.GetDashboardValue(translateShelfToResultType(+oldShelf), 0);
            window.GetDashboardValue(translateShelfToResultType(+shelf), 0);

            window.ePlus.modules.dashboard.refreshWidgetsWithResultType(translateShelfToResultType(+oldShelf));
            window.ePlus.modules.dashboard.refreshWidgetsWithResultType(translateShelfToResultType(+shelf));
        }
    });
}

function DeleteOrder(orderId, confirmText) {
    var orderEnum = window.dashType.order
    if (confirm(confirmText + "?")) {
        $.getJSON("/getJSONData.aspx?builder=DeleteOrders", { key: orderId },
            function (data) {
                if (data.code == "ERROR") {
                    alert(getRes("error_occurred"));
                } else {
                    if (location.hash === "#dashboard") {
                        var resultType = $("#selectedResult_" + orderEnum).val();
                        $("#resultCount_" + orderEnum).val($("#resultCount_" + orderEnum).val() * 1 - 1);
                        closeModalFull(0);
                        $("#value_" + resultType).html($("#resultCount_" + orderEnum).val());
                        $("#headerCount_" + orderEnum).html($("#resultCount_" + orderEnum).val());
                        window.populateOrders(resultType, orderEnum, $("#resultCount_" + orderEnum).val());
                    } else {
                        removeListViewItemAndUpdateListView(orderId);
                    }
                }
            });
    }
}

function removeListViewItemAndUpdateListView(itemId) {
    $("#as_" + itemId).parent().remove();
    removeListViewItem(itemId);
    updateRefineFilterCounts();
    adjustResultTypeCount(getListViewProperty("resultType"), -1);
    adjustTotalResultsCount(-1);
}

function removeListViewItemsAndUpdateListView(itemIds) {
    if (_.isArray(itemIds)) {
        for (var i = 0; i < itemIds.length; i++) {
            $("#as_" + itemIds[i]).parent().remove();
            removeListViewItem(itemIds[i]);
        }
        updateRefineFilterCounts();
        adjustResultTypeCount(getListViewProperty("resultType"), itemIds.length * -1);
        adjustTotalResultsCount(itemIds.length * -1);
        if (window.rows) {
            getResults(window.rows.length, window.sortrefine.length);
            renderPaging(Math.floor((window.rows.length - 1) / 50) + 1);
        }
        changeListPage($("#currentPage").val());

    }
}

function loginRegisterSaveHash() {
    clearPageState(true);
    goToWelcomePage();
}

function clearPageState(doNotCleanPageHash) {
    abortAjaxRequests();
    clearTimeouts();
    closeAllModals();
    closeAllDialogs();
    resetUiElements();
    clearFolderBreadcrumbs();
    removeListViewEventHandlers();
    clearDynamicListViewVariables();
    clearRefinementWindowVariables();
    $('#searchKeywords').val('');
    if (!doNotCleanPageHash) {
        cleanPageHash();
    }
    clearSiteIntervals();
}

function abortAjaxRequests() {
    _.forEach(window.xhrPool, function (jqXhr) {
        if (_.isFunction(jqXhr.abort)) {
            jqXhr.abort();
        }
    });
}

function clearTimeouts() {
    clearTimeout(window.orgThrottleTimeout);
    clearTimeout(window.markupThrottleTimeout);
}

function closeAllModals() {
    $("#popModal").remove();
    $("#popModal_content").remove();
}

function closeAllDialogs() {
    $(".ePlusDialog").remove();
    $(".ePlusDialogModalBlock").remove();
}

function resetUiElements() {
    $('#pageBlockDiv').hide();
    $('#mainBlockDiv').hide();
    $('#tempDataHold').html("");
    $(".homeProcessDiv").remove();
    $('.inProcessDiv').remove();
    WebuiPopovers.hideAll();
    closeListViewAlerts();
    $('body').css("margin-left", "0");
    $(".headerLink").removeClass("selectedTopMenu");
}

function cleanPageHash() {
    //Clear list view args out of hash on homepage
    if (_.startsWith(window.location.hash, "#dashboard")) {
        var newHash = window.location.hash;
        newHash = newHash.replace(/&(page|start|sord|sdir|bookmark)=-?[0-9]+/g, "");
        history.replaceState(null, null, newHash);
    }

    window.location.hashCallback = null;
}

function clearDynamicListViewVariables() {
    window.rows = null;
    window.items = null;
    window.sortrefine = null;
    window.drcs = null;
    window.listView = null;
    window.prices = null;
    window.buyingStores = null;
    window.blockView = null;
    window.catalogID = null;
    window.orderBackbone = null;
}

function clearSiteIntervals() {
    if (window.callAppInsightsDataInterval) {
        clearInterval(window.callAppInsightsDataInterval);
    }

    window.ePlus.modules.dashboard.clearPollItInterval();

    if (window.ePlus.modules.widgetAnalyticsUsage) {
        window.ePlus.modules.widgetAnalyticsUsage.clearAnalyticsUsageInterval();
    }

    if (window.subscriptionInterval) {
        clearInterval(window.subscriptionInterval);
    }
}

function clearRefinementWindowVariables() {
    window.refineMap = null;
    window.combinedRefineMap = null;
    window.refineMapLevels = null;
    window.sortrefine = null;
    window.autoCompleteJson = null;
}

function logoutDueToPageHashRequest() {
    window.goToWelcomePage();

    var returnUrl = getUriHashParameter(window.location.hash, "returnUrl");
    var values = {
        redirectUriOrHash: returnUrl
    };

    $.post('/GetJSONData.aspx?builder=Logout', values, function (data) {
        var newHash = "/";
        if (data && data.length > 0 && data[0].code === 'SUCCESS') {
            var obj = data[0].obj;
            if (obj.isValidRedirectUri) {
                newHash =
                    window.pageHashes.login +
                    "&returnUrl=" +
                    encodeURIComponent(obj.redirectUriOrHash);
            }
        } else {
            alert(window.getRes("error_logging_out"));
        }
        history.pushState(null, null, newHash);
    }, 'json');
}

function checkUrlAndLogin(loginResponse) {
    if (loginResponse.isValidRedirectUri) {
        window.location = loginResponse.redirectUriOrHash;
        return;
    }

    var hash = getLoginHash(loginResponse.redirectUriOrHash);

    if (hash) {
        history.pushState(null, null, hash);
        window.location.reload(true);
    }
}

function getLoginHash(redirectHash) {
    var hashParams = $.deparam(redirectHash || '');
    var returnUrl = null;

    if (hashParams.returnUrl) {
        returnUrl = decodeURIComponent(redirectHash.split('returnUrl=')[1]);
    }

    for (var key in hashParams) {
        if (['Login', 'SignUp'].includes(key) || key.startsWith("//")) {
            delete hashParams[key];
        }
    }

    if (doGoToDashboard(hashParams)) {
        return '#dashboard';
    }

    if (returnUrl) {
        if (returnUrl.startsWith('/')) {
            return returnUrl;
        }

        return '#' + returnUrl;
    }

    return '#' + $.param(hashParams);
}

function doGoToDashboard(hashParams) {
    return !hashParams || Object.keys(hashParams).length === 0 || hashParams.t;
}

function loginAndReturnToCurrentPage() {
    loginAndReturnToPage(window.location.hash);
}

function loginAndReturnToPage(newHash) {
    var returnHash = removeLeadingHash(newHash);
    var newLocation = '#Login';
    if (returnHash) {
        newLocation += '&returnUrl=' + returnHash;
    }
    window.location = newLocation;
}

function loginAndReturnToSkuPage(sku) {
    loginAndReturnToPage('#sku=' + sku);
}

function removeLeadingHash(hash) {
    return _.isString(hash) && hash.length > 0 ? hash.substring(1) : null;
}

/**
 * Builds a URI hash string from 'params'.
 * @param {object} [params] - Object containing key/value pairs to turn into a hash; null/undefined will be treated as {}
 * @param {boolean} [doAddTimestamp] - Optional: Adds a _=[timestamp] parameter to force a hash change event
 * @returns {string} URI hash string
 */
function buildUriHash(params, doAddTimestamp) {
    params = params || {};

    if (doAddTimestamp) {
        params['_'] = (+new Date());
    }

    return $.param(params);
}

/**
 * Returns an object representation of URI hash string.
 * @param {string} hash - Hash string (leading # will be ignored)
 * @returns {object} The URI hash object
 */
function getUriHashParameters(hash) {
    if (!_.isString(hash)) return {};

    if (hash.length && hash[0] === '#') {
        hash = hash.substring(1);
    }

    return $.deparam(hash);
}

/**
 * Returns the value for a paramater in the specified hash
 * @param {string} hash - Hash string (leading # will be ignored)
 * @param {string} parameterName
 * @returns null or parameter value
 */
function getUriHashParameter(hash, parameterName) {
    var paramValue = null;
    var currentParams = getUriHashParameters(window.location.hash);
    if (currentParams && currentParams.hasOwnProperty(parameterName)) {
        paramValue = currentParams[parameterName];
    }
    return paramValue;
}

/**
 * Adds, updates, or deletes a key in the provided URI hash string.
 * @param {string} hash - Hash string (leading # will be stripped)
 * @param {string} [key] - Optional - Parameter key to add or update
 * @param {string} [value] - Optional - Parameter value to add or update (if null/undefined, the param will be removed)
 * @returns {string} The modified URI hash string
 */
function updateUriHashParameter(hash, key, value) {
    var params = getUriHashParameters(hash);

    if (_.isString(key) && !_.isNil(value)) {
        params[key] = value;
    } else if (_.isString(key)) {
        delete params[key]; // If a value isn't provided, remove the key
    }

    return $.param(params);
}

/**
 * Reloads the current page (hash).
 * NOTE: Adds or updates the a _=[timestamp] URL hash string parameter to force hash change events
 * @param {function} [callback] - Reload page callback
 */
function reloadPage(callback) {
    var hash = updateUriHashParameter(window.location.hash, '_', (+new Date()));
    pageChange(hash, callback);
}

/**
 * Checks the DOM for the specified dashboard lane.
 * @param {number} dashType - DashType of the dashboard lane
 * @returns {boolean} - Returns true if the dashboard lane exists
 */
function doesDashboardLaneExist(dashType) {
    return $("#dash_" + dashType).length > 0;
}

/**
 * Checks if the page is a list view.
 * @param {object} [filter] - Optional filter object to check various listView property values
 * @returns {boolean} Returns true if the user is in a list view with the specified filters
 */
function isInListView(filter) {
    return window.listView && (_.isNil(filter) || _.isMatch(window.listView, filter));
}

/**
 * Saves the result type for the specified dashboard.
 * @param {number} dashType - DashType to save
 * @param {number} resultType - ResultType to save
 * @param {function} [callback] - Callback after the preference is saved
 */
function saveDashboardResultType(dashType, resultType, callback) {
    savePreference('dashboard', 'resultTypes_' + dashType, resultType, callback);
}

/**
 * Refreshs the specified dashboard.
 * @param {number} dashType - DashType of the dashboard lane to refresh
 * @callback {function} callback - Callback after the dashboard lane is refreshed
 * TODO: Add support for specifying an optional resultType
 */
function refreshDashboardLane(dashType, callback) {
    var resultType = $("#selectedResult_" + dashType).val();

    refreshThumbnails(resultType, dashType, 10);

    // TODO: Technically the callback should probably occur in refreshThumbnails, but this is okay for now
    if (_.isFunction(callback)) {
        callback();
    }
}

/**
 * Refreshes catalogs in the catalog dashboard lane or list view; or titles in the specified catalog's title list view.
 * @param {any} catalogId - CatalogId of the catalog
 * @param {any} callback - Callback after refreshing the dashboard lane or list view
 */
function refreshCatalogsOrCatalogTitles(catalogId, callback) {
    var dashCatalog = getEnumValue('dashType', 'DASHCATALOG');

    // If we're on the dashboard and the catalog lane exists, refresh the lane
    if (doesDashboardLaneExist(dashCatalog)) {
        refreshDashboardLane(dashCatalog, callback);
        // If we're in a catalog or title list view (in the specified catalog), refresh the page to update the list and refinements
    } else if (
        isInListView({ itemType: getEnumValue('itemType', 'CATALOG') }) ||
        isInListView({ itemType: getEnumValue('itemType', 'TITLE'), itemID: catalogId })
    ) {
        reloadPage(callback);
    } else if (typeof callback === 'function') {
        callback();
    }
}

///////////////////////////////////////////////////////////////////////////////
// Navigation Functions
///////////////////////////////////////////////////////////////////////////////

/**
 * Navigates to the specified list view.
 * @param {number} [dashType] - DashType of the list view to load
 * @param {number} [resultType] - ResultType of the list view to load
 * @param {function} [callback] - Callback after the list view is loaded
 */
function goToListView(dashType, resultType, callback) {
    var params = {};

    if (dashType) {
        params.dashList = dashType;
    }

    if (resultType) {
        params.rT = resultType;
    }

    saveDashboardResultType(dashType, resultType, function (data) {
        var hash = buildUriHash(params, true);
        pageChange(hash, callback);
    });
}

/**
 * Navigates to the specified catalog.
 * @param {number} catalogId - CatalogId of catalog
 * @param {number} [mailingId] - MailingId of optional markup
 * @param {function} [callback] - Callback after catalog is loaded
 */
function goToCatalog(catalogId, mailingId, callback) {
    var params = {
        catalogID: catalogId
    };

    if (mailingId) {
        params.mailingID = mailingId;
    }

    var hash = buildUriHash(params, true);

    pageChange(hash, callback);
}

/**
 * Navigates to the specified publisher.
 * @param {string} orgId - OrgId of the publisher
 * @param {function} [callback] - Callback after publisher is loaded
 */
function goToPublisher(orgId, callback) {
    var hash = buildUriHash({
        publisher: orgId
    });

    pageChange(hash, callback);
}

/**
 * Navigates to the collections list view.
 * @param {function} [callback] - Callback after the collections list view is loaded
 */
function goToCollections(callback) {
    var dashType = getEnumValue('dashType', 'DASHCATALOG'),
        resultType = getEnumValue('resultType', 'CATALOGCOLLECTIONS');

    goToListView(dashType, resultType, callback);
}

function changeFeature(sku, catalogId) {
    var $columnClicked = $('#feature_' + sku);
    $.post("/getJSONData.aspx?m=Catalog&builder=ChangeFeature", { sku: sku, catalogID: catalogId },
        function (data) {
            if (data.code === "SUCCESS") {
                if (data.data === "0") {
                    $columnClicked.removeClass('icon-rating-active-icon').addClass('icon-rating-inactive-icon');
                } else {
                    $columnClicked.removeClass('icon-rating-inactive-icon').addClass('icon-rating-active-icon');
                }
            }
            else {
                alert(data.text);
            }
        }, "json");
}

function updateProductCatalogPrint(catalogId, sku, print) {
    $.ajax({
        type: "POST",
        cache: false,
        url: "/api/catalogs/" + catalogId + "/products/" + sku + "/catalogPage/" + print,
        contentType: "application/json"
    });
}

function updateProductCatalogSpan(catalogId, sku, span) {
    $.ajax({
        type: "POST",
        cache: false,
        url: "/api/catalogs/" + catalogId + "/products/" + sku + "/numPages/" + span,
        contentType: "application/json"
    });
}

function getProductCatalogAttributes(sku) {
    $.ajax({
        type: "GET",
        cache: false,
        url: "api/products/" + sku + "/all?apiView=standard,advanced,localizedDateTimes",
        contentType: "application/json",
        success: function (data) {
            if (data && data.sku) {
                updateAdminGrid(data);
                updateAdminGridToolTips(data);
            }
        }
    });
}

function updateAdminGridName(data) {
    $('#productName_' + data.sku)
        .html(data.fullName)
        .attr('title', data.fullName);
}

function updateAdminGridPublishingStatus(data) {
    $('#publishing-status-' + data.sku)
        .attr('data-publishing-status-code', data.onixPublishingStatusCode || '')
        .attr('data-publishing-status-text', data.publishingStatus || '')
        .attr('title', data.publishingStatus || '');

    if (typeof treeline !== 'undefined') {
        treeline.updatePublishingStatusIndicators(data.sku);
    }
}

function updateAdminGridPublisherDate(data) {
    $('#productPubDate_' + data.sku).html(data.localizedShortPubDate || '');
}

function updateAdminGridFormat(data) {
    $('#productFormat_' + data.sku)
        .html(data.format || '')
        .attr('title', data.format);
}

function updateAdminGrid(data) {
    var sku = data.sku;

    updateAdminGridName(data);
    updateAdminGridPublishingStatus(data);
    updateAdminGridPublisherDate(data);
    updateAdminGridFormat(data);

    updateAdminGridImageColumnStatus($('#jacketCover_' + sku), data.images, 'jacketCover', data.targetGroupID, true);
    updateAdminGridImageColumnStatus($('#number_Illustrations_' + sku), data.images, 'interior', data.targetGroupID, false);

    updateAdminGridDescriptionColumnStatus($('#char_AuthorBio_' + sku), data.completionSummary, 'char_AuthorBio');
    updateAdminGridDescriptionColumnStatus($('#char_Excerpts_' + sku), data.completionSummary, 'char_Excerpts');
    updateAdminGridDescriptionColumnStatus($('#char_FeaturePoints_' + sku), data.completionSummary, 'char_FeaturePoints');
    updateAdminGridDescriptionColumnStatus($('#char_KeySellingPoints_' + sku), data.completionSummary, 'char_KeySellingPoints');

    updateAdminGridDescriptionColumnStatus($('#char_KeyNote_' + sku), data.completionSummary, 'char_KeyNote');
    updateAdminGridDescriptionColumnStatus($('#char_Mktg_' + sku), data.completionSummary, 'char_Mktg');
    updateAdminGridDescriptionColumnStatus($('#char_Quotes_' + sku), data.completionSummary, 'char_Quotes');
    updateAdminGridDescriptionColumnStatus($('#char_Summary_' + sku), data.completionSummary, 'char_Summary');
    updateAdminGridDescriptionColumnStatus($('#char_TableOfContents_' + sku), data.completionSummary, 'char_TableOfContents');
    updateAdminGridDescriptionColumnStatus($('#char_UnpublishedEndorsements_' + sku), data.completionSummary, 'char_UnpublishedEndorsements');

    updateAdminGridDescriptionColumnStatus($('#number_Links_' + sku), data.completionSummary, 'number_Links');
    updateAdminGridDescriptionColumnStatus($('#number_Videos_' + sku), data.completionSummary, 'number_Videos');
    updateAdminGridDescriptionColumnStatus($('#number_Comps_' + sku), data.completionSummary, 'number_Comps');

    updateAdminGridDescriptionColumnStatusWithoutAudience($('#number_SalesRights_' + sku), data.completionSummary, 'numberSalesRights');

    updateAdminGridDescriptionColumnStatus($('#number_RelatedProducts_' + sku), data.completionSummary, 'number_RelatedProducts');
}

function updateAdminGridMediaColumnStatus($element, array) {
    var hasAttribute = false;

    if (!_.isNil(array)) {
        hasAttribute = true;
    }

    $element.removeClass().addClass(getAdminColumnStatusClass(hasAttribute, false));
}

function updateAdminGridMediaColumnStatusWithAudienceId($element, array, audienceId) {
    var hasAttribute = false;
    var showWarningIcon = false;

    if (!_.isNil(array) && _.find(array, { 'targetGroupID': audienceId })) {
        hasAttribute = true;
    }

    if (!hasAttribute) {
        if (!_.isNil(array)) {
            hasAttribute = true;
            showWarningIcon = true;
        }
    }

    $element.removeClass().addClass(getAdminColumnStatusClass(hasAttribute, false, showWarningIcon));
}

function updateAdminGridDescriptionColumnStatus($element, descriptions, attribute) {
    var hasAttribute = false;

    if (!_.isNil(descriptions) && !_.isNil(descriptions.sourceAudienceMap[attribute]) && descriptions.sourceAudienceMap[attribute] > 0) {
        hasAttribute = true;
    }
    var showWarningIcon = updateAdminGridDescriptionAudience(attribute, descriptions.audienceID, descriptions.sourceAudienceMap);
    $element.removeClass().addClass(getAdminColumnStatusClass(hasAttribute, false, showWarningIcon));
}

function updateAdminGridDescriptionColumnStatusWithoutAudience($element, descriptions, attribute) {
    var hasAttribute = false;

    if (!_.isNil(descriptions) && !_.isNil(descriptions[attribute]) && descriptions[attribute] > 0) {
        hasAttribute = true;
    }
    var showWarningIcon = updateAdminGridDescriptionAudience(attribute, descriptions.audienceID, descriptions.sourceAudienceMap);
    $element.removeClass().addClass(getAdminColumnStatusClass(hasAttribute, false, showWarningIcon));
}

function updateAdminGridDescriptionAudience(attribute, audiencedId, sourceAudienceMap) {
    var item = _.find(sourceAudienceMap,
        function (value, key) {
            if (_.startsWith(key, attribute)) {
                return value;
            }
        });

    if (!_.isNil(item) && audiencedId !== item) {
        return true;
    }

    return false;
}

function updateAdminGridImageColumnStatus($element, images, imageType, audienceId, showErrorIcon) {
    var hasAttribute = false;

    if (!_.isNil(images) && _.find(images, { 'type': imageType, 'targetGroupId': audienceId })) {
        hasAttribute = true;
    }

    $element.removeClass().addClass(getAdminColumnStatusClass(hasAttribute, showErrorIcon));
}

function getAdminColumnStatusClass(hasAttribute, showErrorIcon, showWarningIcon) {
    var canShowWarningId = !_.isNil(showWarningIcon) ? showWarningIcon : false;

    if (hasAttribute === true && !canShowWarningId) {
        return "icon-mark-icon imageOkay";
    } else if (showErrorIcon) {
        return "icon-close-icon imageError";
    } else if (showWarningIcon) {
        return "icon-exclamation-triangle imageWarning";
    }
    else {
        return "";
    }
}

function updateAdminGridToolTips(data) {
    var keys = "char_Summary,char_AuthorBio,char_Quotes,char_Excerpts,char_Mktg,char_KeySellingPoints,char_FeaturePoints,char_KeyNote,char_UnpublishedEndorsements,char_TableOfContents,number_Comps,number_RelatedProducts,number_Illustrations,number_Links,number_Videos,number_SubRights,number_SalesRights";
    var postParams = JSON.stringify(keys);

    var sku = data.sku;

    $.ajax({
        type: "POST",
        cache: false,
        data: postParams,
        url: "/api/products/completionSummaries/audienceNames/" + sku,
        contentType: "application/json",
        success: function (audienceNames) {
            updateColumnToolTip($('#char_AuthorBio_' + sku).parent(), data.completionSummary, 'charCountAuthorBio', audienceNames['char_AuthorBio']);
            updateColumnToolTip($('#char_Excerpts_' + sku).parent(), data.completionSummary, 'charCountExcerpts', audienceNames['char_Excerpts']);
            updateColumnToolTip($('#char_FeaturePoints_' + sku).parent(), data.completionSummary, 'charCountFeaturePoints', audienceNames['char_FeaturePoints']);
            updateColumnToolTip($('#char_KeySellingPoints_' + sku).parent(), data.completionSummary, 'charCountKeySellingPoints', audienceNames['char_KeySellingPoints']);
            updateColumnToolTip($('#char_KeyNote_' + sku).parent(), data.completionSummary, 'charCountKeyNote', audienceNames['char_KeyNote']);

            updateColumnToolTip($('#char_Mktg_' + sku).parent(), data.completionSummary, 'charCountMarketing', audienceNames['char_Marketing']);
            updateColumnToolTip($('#char_Quotes_' + sku).parent(), data.completionSummary, 'charCountQuotes', audienceNames['char_Quotes']);
            updateColumnToolTip($('#char_Summary_' + sku).parent(), data.completionSummary, 'charCountSummary', audienceNames['char_Summary']);
            updateColumnToolTip($('#char_TableOfContents_' + sku).parent(), data.completionSummary, 'charCountTableOfContents', audienceNames['char_TableOfContents']);
            updateColumnToolTip($('#char_UnpublishedEndorsements_' + sku).parent(), data.completionSummary, 'charCountUnpublishedEndorsements', audienceNames['char_UnpublishedEndorsements']);

            updateColumnToolTip($('#number_Links_' + sku).parent(), data.completionSummary, 'numberLinks', audienceNames['number_Links']);
            updateColumnToolTip($('#number_Videos_' + sku).parent(), data.completionSummary, 'numberVideos', audienceNames['number_Videos']);
            updateColumnToolTip($('#number_Comps_' + sku).parent(), data.completionSummary, 'numberComps', audienceNames['number_Comps']);
            updateColumnToolTip($('#number_SalesRights_' + sku).parent(), data.completionSummary, 'numberSalesRights', audienceNames['number_SalesRights']);
            updateColumnToolTip($('#number_RelatedProducts_' + sku).parent(), data.completionSummary, 'numberRelatedProducts', audienceNames['number_RelatedProducts']);
        }
    });
}

function updateColumnToolTip($element, data, attribute, audienceName) {
    var characterCount = 0;
    var description = "";
    var market = "";
    if (!_.isNil(data) && !_.isNil(data[attribute])) {
        characterCount = data[attribute];
    }

    var updatedToolTipValue = getColumnToolTipValue($element, characterCount, audienceName);
    $element.attr("title", updatedToolTipValue);
}

function getColumnToolTipValue($element, characterCount, audienceName) {
    var title = "";
    if ($element.attr("title")) {
        var indexOfSemiColon = $element.attr("title").indexOf(":");
        title = $element.attr("title").substring(0, indexOfSemiColon + 1);
    }

    var market = "[" + audienceName + "]";

    return title + " " + characterCount + " " + market;
}

function showIsbnResultsTable(id, sku, orgId, pubOrgId, audienceId, showPostedDate) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/lookup/ProductLookup_Catalogs.ascx" +
        "&sku=" + sku + "&orgID=" + orgId + "&pubOrgId=" + pubOrgId + "&audienceID=" + audienceId + "&scrollBar=300px&showPostedDate=" + showPostedDate;

    $('#' + id).load(url);
}

function refreshAdminGrid(catalogId, resultType) {
    closeModal();
    savingModalOverlay('<%=GetLocalizedString("saving")%>', "productAdminGrid", null, "white-fill");
    openEditCatalog(catalogId, resultType);
}

///////////////////////////////////////////////////////////////////////////////
// Functions for Managing contacts
///////////////////////////////////////////////////////////////////////////////
function goToLookUpUser() {
    $("#peopleScroller").hide();
    $("#contactAdd").show();
    $("#addNewContactDiv").hide();
    $("#cancelNewContactDiv").show();
    $.url = "/GetTreelineControl.aspx?controlName=/uc/contacts/lookUpUser.ascx";
    $("#contactAdd").load($.url);
}

function cancelLookUpUser() {
    $("#peopleScroller").show();
    $("#contactAdd").hide();
    $("#addNewContactDiv").show();
    $("#cancelNewContactDiv").hide();
}

/**
 * reload Nav Filter Options
 */
function reloadNavFilterOptions() {
    if ($("#leftNavOptions").length > 0) {
        $.url = "/GetTreelineControl.aspx?controlName=/uc/listviews/ListHomeSavedFilterOptions.ascx&dashType=" + window.dashType.filters + "&viewID=" + getListViewProperty("itemID");
        $("#leftNavOptions").load($.url, function () {
            reloadList();
        });
    }
}

var compSearchResultsDigitalFilter = function (oSettings, aData, iDataIndex) {
    if (oSettings.nTable.id === "compSearchResultsTable") {
        var doHideDigitalFormats = $("#doHideDigitalFormats").hasClass("box_checked");
        var rowClass = oSettings.aoData[iDataIndex].nTr.className;
        if (rowClass && rowClass.indexOf('non-digital') === - 1) {
            return doHideDigitalFormats ? false : true;
        } else {
            return true;
        }
    } else {
        return true;
    }
}

function changeSelectedFilterFromListView(viewID, filterName) {
    $("#savedFilterName").html(filterName);
    $("#savedFilterName").attr("data-viewid", viewID);
    saveFilterPref(viewID, getListViewProperty("dashboardType"));
}

/**
 * Initialize custom data table filters. Clears current custom filters.
 * @returns {boolean} Result of initialization
 */
function initCustomDataTableFilters() {
    try {
        clearCustomDataTableFilters();
        $.fn.dataTable.ext.search.push(compSearchResultsDigitalFilter);
        return true;
    } catch (ex) {
        console.warn("Error initializing custom datatable filters. \n" + ex);
        return false;
    }
}

function clearCustomDataTableFilters() {
    if ($) {
        $.fn.dataTable.ext.search = [];
        return true;
    }
    return false;
}

function onScrollEvent() {
    var amountScrolled = 300;

    if ($(window).scrollTop() > amountScrolled) {
        $('.back-to-top').fadeIn('slow');
    } else {
        $('.back-to-top').fadeOut('slow');
    }

    if ($("#scrollpos_top").length > 0) {
        var position = $("#scrollpos_top").offset();
        $("#scrollpos_top").val(position.top);
        if ($("#popUpControl").val() != "") {
            showGenericPop_FullWidth($("#popUpControl").val(), $("#popUpControlParent").val(), 'scroll', 0, 0);
        }
    }
}

function onInteractionEvent(event, callback, cancelCallback) {
    // If a page interaction handler is defined, defer to it;
    // otherwise, proceed with the intended interaction
    if (typeof window.interactionHandler === 'function') {
        window.interactionHandler(event, callback, cancelCallback);
    } else if (typeof callback === 'function') {
        callback(event);
    }
}

function sendFriendInvite(index) {
    var showStatusOfRequest = false;
    if (typeof index !== "undefined") {
        showStatusOfRequest = true;
        $("#requestFriendIcon_" + index).addClass("pending-color");
    }
    $.getJSON("/getJSONData.aspx?builder=AddFriend",
        {
            orgID: $("#cSelected_orgID").val(),
            userID: $("#cSelected_userID").val()
        },
        function (data) {
            if (showStatusOfRequest) {
                $("#requestFriendIcon_" + index).removeClass("pending-color").addClass("success-color").delay(2000).queue(function () {
                    $(this).removeClass("success-color").dequeue().fadeOut();
                });
            }
            var outboundRelationshipId = getEnumValue("relationshipDirection", "OUTBOUND");
            $("#friendIcon_" + outboundRelationshipId + "_" + $("#cSelected_ID").val()).css("display", "");
            $("#c_friendStatus" + $("#cSelected_ID").val()).val(outboundRelationshipId);
            $("#c_AddFriend").hide();
            $("#c_CancelRequest").show();
            $("#outbound-invites").show();

            adjustCount("#modal_" + getEnumValue("userAdminArea", "OUTBOUNDINVITES") + "-count", 1);
        });
}

/**
 * Encode text as Html for safe use in the DOM.
 * @param {string/number} value
 * @returns {string} Html encoded string
 */
function htmlEncode(value) {
    //create a in-memory div, set it's inner text(which jQuery automatically encodes)
    //then grab the encoded contents back out.  The div never exists on the page.
    return $('<div/>').text(value).html();
}

/**
 * Decode Html string to plain text
 * @param {string} value
 * @returns {string} Html decoded string
 */
function htmlDecode(value) {
    return $('<div/>').html(value).text();
}

/**
 * Encode text as safe for use in an Html attribute
 * @param {string/number} value
 * @returns {string} Html attribute encoded string
 */
function htmlAttributeEncode(value) {
    return ('' + value)
        .replace(/&/g, '&amp;')
        .replace(/'/g, '&apos;')
        .replace(/"/g, '&quot;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;');
}

/**
 * Decode Html attribute encoded string to plain text
 * @param {string} value
 * @returns {string} Html attribute decoded string
 */
function htmlAttributeDecode(value) {
    return value
        .replace(/&apos;/g, '\'')
        .replace(/&quot;/g, '"')
        .replace(/&lt;/g, '<')
        .replace(/&gt;/g, '>')
        .replace(/&amp;/g, '&');
}

/**
 * Determine if user agent reflects an iOS device
 * @returns {boolean}
 */
function isIOs() {
    return /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
}

function toggleJustMyAccountsCheckbox(parent, orgId, userId, prefType, prefName) {
    var $parent = $(parent);
    var $checkbox = $parent.find('.just-my-accounts-checkbox');

    $checkbox.toggleClass('box_unchecked box_checked');

    var isChecked = $checkbox.hasClass("box_checked") ? '1' : '0';
    var url = "api/users/" + orgId + "/" + userId + "/preferences/" + prefType + "/" + prefName;

    $.ajax({
        type: "POST",
        data: isChecked,
        cache: false,
        url: url,
        contentType: "application/json"
    }).done(function () {
        reloadList();
    });
}

function saveMarkupDetails(mailingId, catalogId, name, overview, httpSessionStateVersion) {
    var values = {
        catalogId: catalogId,
        name: name,
        message: overview,
        httpSessionStateVersion: httpSessionStateVersion
    };

    $.ajax({
        type: 'POST',
        url: "api/markups/" + mailingId,
        data: JSON.stringify(values),
        contentType: "application/json",
    }).done(function (ret) {
        var $selectedMailingName = $("#selectedMailingName");
        var $markupName = $("#markupName_" + mailingId);
        var markupName = ret.name;

        if ($selectedMailingName && $("#markup_" + mailingId).hasClass("box_checked")) {
            $selectedMailingName.html(markupName);
        }

        if ($markupName) {
            $markupName.html(markupName);
        }

        closeModal();
    }).fail(function (ret) {
        var res = ret.responseText;
        var resJson = JSON.parse(res);
        var message = resJson.message;

        alert(message);
    });
}

function shareMarkupWithAllOrgs(mailingId, catalogId) {
    var orgList = getOrgListFromOrgRecordClass();

    if (!_.isNil(orgList)) {
        $.post("/getJSONData.aspx?builder=ShareMarkup_Simple",
            {
                idList: orgList,
                mailingID: mailingId,
                catalogID: catalogId,
                mailingName: encodeURIComponent($("#selectedMailingName").html())
            },
            function (data) {
                loadSharedWith(true);
            });
    }
}

function unshareMarkupWithAllOrgs(mailingId, catalogId) {
    var orgList = getOrgListFromOrgRecordClass();

    if (!_.isNil(orgList)) {
        $.post("/getJSONData.aspx?builder=UnShareMarkup",
            {
                id: orgList,
                mailingID: mailingId,
                catalogID: catalogId,
                mailingName: encodeURIComponent($("#selectedMailingName").html())
            },
            function (data) {
                loadSharedWith(true);
            });
    }
}

function getOrgListFromOrgRecordClass() {
    return encodeURI($(".orgRecord").map(function () {
        return $(this).attr("data-orgId");
    }).get().join());
}

function getCollectionName(collectionId) {
    return $('#collection_' + collectionId).data('name') || $("#collectionName").val();
}

function getCollectionInfoIfAddedToSingleCollection(collectionIdsAdded) {
    if (!_.isString(collectionIdsAdded)) return;

    var collectionIds = _.reduce(collectionIdsAdded.split(','), function (acc, value) {
        if (value) {
            acc.push(parseInt(value));
        }
        return acc;
    }, []),
        collectionId = _.first(collectionIds);

    if (collectionIds.length !== 1 || collectionId <= 0) return;

    return {
        collectionId: collectionId,
        collectionName: getCollectionName(collectionId)
    }
}

function goToEditCollection(collectionInfo) {
    if (!collectionInfo) return;

    closeModal();
    goToCatalog(collectionInfo.collectionId, 0, function () {
        openEditCatalog(collectionInfo.collectionId, getEnumValue('resultType', 'TITLE_CATALOG'));
    });
}

function createPublisherSelectMenu() {
    $('#publisherMenu').webuiPopover({
        type: 'async',
        cache: false,
        backdrop: true,
        url: "/GetTreelineControl.aspx?controlName=/uc/organization/publisherSelect.ascx"
    });
}

function saveTourInformation(campaignId, sku) {
    if (_.isNil(campaignId) || _.isNil(sku)) {
        return;
    }

    var url = "/api/publicityCampaigns/" + campaignId + "/titles/" + sku + "/tourInformation";

    var updatedTourInfo = {
        author: $("#author").val(),
        authorNamePronunciation: $('#authorNamePronunciation').val(),
        authorPronouns: $('#authorPronouns').val(),
        tourLocations: $("#tourLocations").val(),
        hometown: $("#hometown").val(),
        publicist: $("#publicist").val(),
        comments: $("#tourComments").val()
    };

    $.ajax({
        type: "POST",
        data: JSON.stringify(updatedTourInfo),
        cache: false,
        url: url,
        contentType: "application/json",
        success: function (data) {
            $("#authorDisplay" + sku).html(updatedTourInfo.author);
            $("#locationsDisplay" + sku).html(updatedTourInfo.tourLocations);
            $("#hometownDisplay" + sku).html(updatedTourInfo.hometown);

            if ($("#publicistDisplay" + sku).length) {
                $("#publicistDisplay" + sku).html(updatedTourInfo.publicist);
            }

            $("#commentsDisplay" + sku).html(updatedTourInfo.comments);
            $('#authorNamePronunciationDisplay' + sku).html(updatedTourInfo.authorNamePronunciation);
            $('#authorPronounsDisplay' + sku).html(updatedTourInfo.authorPronouns);
            closeModal();
        }
    });
}

function AddRemoveEventGridAccess(contactID, checkElem) {
    var action = null;
    var refineValue = 0;

    if (checkElem.is(':checked')) {
        action = "add";
        refineValue = 0;
    }
    else {
        action = "remove";
        refineValue = 1;
    }
    if (action) {
        jQuery.post("/GetJSONData.aspx?builder=AddRemoveGridAccess", { contactID: contactID, action: action },
            function (data) {
                if (data.code == "SUCCESS") {
                    if (window.sortrefine) {
                        var idx = _.findIndex(window.sortrefine, { item: "" + contactID });
                        if (idx > -1) {
                            window.sortrefine[idx]["hasEventGridAccess"] = refineValue;
                        }
                        getRefineNumbers();
                    }
                }
                else {
                    alert(data.text);
                }
            }, "json");
    }
}

function clearFolderBreadcrumbs() {
    if (!_.isNil(getListViewProperty("resultType"))) {
        var isLeavingPublisherPageGoingToTitleView = ((getListViewProperty("resultType") === getEnumValue("resultType", "CATALOGFOLDERS"))
            && (getHashValue("catalogID") !== null));

        var isLeavingTitleViewGoingToPublisherPage = (getListViewProperty("resultType") === getEnumValue("resultType", "TITLE_CATALOG"))
            && (getHashValue("publisher") !== null && getHashValue("folderID") !== null);

        if (!(isLeavingPublisherPageGoingToTitleView) && !(isLeavingTitleViewGoingToPublisherPage)) {
            sessionStorage.removeItem("folderBreadcrumbs")
        }
    }
}

function getResultTypeHash(dashboardType, resultType) {
    if (_.isNumber(dashboardType) && _.isNumber(resultType)) {
        return "#dashList=" + dashboardType + "&rT=" + resultType;
    } else {
        return "#dashboard";
    }
}

function loadDashSplit() {
    $.url = "/GetTreelineControl.aspx?controlName=/uc/dashboard_v2/DashSplit.ascx";
    $("#interiorPageContent").load($.url, function () {
        if (typeof pageTracking !== 'undefined') {
            pageTracking.stopTrackPage();
        }
    });
}

function deleteSelectedContactsWithConfirmation() {
    var contactIds = getSelectedItems();

    if (!contactIds || contactIds.length === 0) {
        modalAlert(getRes("must_select_one_or_more"));
        return;
    }

    var modalButtons = {};

    modalButtons[getRes('yes')] = function () {
        deleteSelectedContacts();
        closeModal();
    };

    modalButtons[getRes('no')] = function () {
        closeModal();
    };

    modalConfirm({
        message: getRes('confirm_delete_contacts'),
        width: "340px",
        height: "150px",
        buttons: modalButtons
    });
}

function deleteSelectedContacts() {
    var contactIds = getSelectedItems();

    $.ajax({
        type: 'DELETE',
        url: '/api/me/contacts',
        contentType: "application/json",
        data: JSON.stringify(contactIds)
    }).done(function () {
        reloadCurrentPage();
    });
}

function deleteContactWithConfirmation(emailAddress, deleteConfirmation) {
    var postData = {
        emailList: emailAddress
    }

    if (confirm(deleteConfirmation)) {
        $.post("/getJSONData.aspx?builder=DeleteContacts_EmailAddress", postData,
            function (data) {
                reloadCurrentPage();
            }, "json");
    }
    return false;
}

function downloadFile(uri, contactIds) {
    var form = $('<form />', {
        method: 'POST',
        action: uri,
        target: '_blank',
        css: {
            display: 'none'
        }
    });

    if (contactIds) {
        for (var i in contactIds) {
            var contactId = contactIds[i];

            $('<input>').attr({
                value: contactId,
                name: 'ContactIds'
            }).appendTo(form);
        }
    }

    form.appendTo(document.body).submit();
}

function initDashItemsPopovers($element, container, userId, resultType) {
    $element.webuiPopover({
        type: 'async',
        trigger: "hover",
        cache: false,
        container: container,
        url: "/GetTreelineControl.aspx?controlName=/uc/dashboard_v2/DashItems.ascx&userID=" + userId + "&resultType=" + resultType + "&laneID=0&widgetID=0&popoverId=" + $element.attr("id")
    });
}

function deleteOrganizationContacts(orgIds) {
    var postParams = JSON.stringify(orgIds);

    $.ajax({
        type: "DELETE",
        data: postParams,
        cache: false,
        url: "/api/me/contacts/organizations/delete",
        contentType: "application/json",
        success: function () {
            var resultType = window.getListViewProperty("resultType");
            if (resultType === window.getEnumValue("resultType", "PEOPLE_CONTACTS_ORGANIZATIONS")) {
                reloadCurrentPage();
            }
        }
    });
}

function shareMarkupsWithSelectedAndEmail() {
    var idList = $(".toShareAddr").map(function () {
        return encodeURI($(this).attr("key"));
    }).get().join(",");

    var sendEmail = $('#sendEmail').is(':checked');

    if (!_.isNil(idList)) {
        $.post("/getJSONData.aspx?builder=ShareMarkup_Simple",
            {
                idList: idList,
                mailingID: $('#selectedEntityID').val(),
                catalogID: $("#selectedCatalogID").val(),
            },
            function (data) {
                if (sendEmail) {
                    shareMarkupEmail(idList);
                }
            });
    }
}

function getOrganizationContactCount(orgId, successCallback) {
    var url = "/api/me/contacts/organizations/count/" + orgId;

    $.ajax({
        type: "GET",
        url: url,
        cache: false,
        contentType: "application/json",
        success: function (data) {
            if (typeof successCallback === "function") {
                successCallback(orgId, data);
            }
        }
    });
}

function updateOrganizationRowContactCount(orgId, orgContactCount) {
    $("#orgContactCount_" + orgId).html(orgContactCount);
    closeModal();
}

function printExportTable() {
    printSelectedArea('export-preview-table', true, false, "/css/export-preview-print.css");
}

function enableCategoriesToggle(container) {
    container = container || '#itemContainer';

    $('ul.categories a.toggle', container).off().on('click', function (e) {
        e.preventDefault();
        $(this).toggleClass('icon-drop-down-icon icon-drop-up-icon-01').closest('ul.categories').toggleClass('expand collapse');
        return false;
    });
}

function enablePeerNotesToggle(container) {
    container = container || '#itemContainer';

    $('span.shared-notes-toggle', container).off().on('click', function (e) {
        e.preventDefault();

        var sku = $(this).attr('data-sku');
        $("span.iconDropDown", $(this)).toggleClass('icon-drop-down-icon icon-drop-up-icon-01');
        $('#shared-markup-notes-' + sku).toggleClass('hidden');
        return false;
    });
}

function printSelectedArea(elementId, doPrint, doCloseAfterPrint, customStyleSheet) {
    if (!elementId) return;
    var elementToPrint = document.getElementById(elementId);
    var printWindow = window.open('');
    var html = elementToPrint.outerHTML;
    if (customStyleSheet) {
        html = '<head><link type="text/css" rel="stylesheet" href="' + customStyleSheet + '?dt=' + new Date().getTime() + '"/></head>' + html;
    }

    printWindow.document.write(html);
    printWindow.focus();
    printWindow.document.close();
    if (doPrint) {
        setTimeout(function () {
            printWindow.print();
            if (doCloseAfterPrint) {
                printWindow.close();
            }
        }, 100);
    }
}

function checkCollections(catalogId, sku) {
    if ($("#cl_" + sku + "_" + catalogId).hasClass("box_checked")) {
        $("#cl_" + sku + "_" + catalogId).removeClass("box_checked");
        $("#cl_" + sku + "_" + catalogId).addClass("box_unchecked");
    } else {
        $("#cl_" + sku + "_" + catalogId).addClass("box_checked");
        $("#cl_" + sku + "_" + catalogId).removeClass("box_unchecked");
    }

    toggleAddCollectionSaveAndEdit();
}

function toggleAddCollectionSaveAndEdit() {
    $collectionsChecked = $((".addCollectionOption.box_checked"), $("#collectionList"));

    var collectionsSelected = $collectionsChecked.length + $("#createNewCollection.box_checked").length;
    if (collectionsSelected === 1) {
        $("#collection-save-edit").show();
    }
    else {
        $("#collection-save-edit").hide();
    }
}

function saveProfile() {
    $("#saveDiv").hide();
    var values = {
        userProfile: encodeURI($("textarea#personalProfile").val())
    };

    $.post("/GetJSONData.aspx?m=User&builder=saveProfile", values, function (data) {
        if (data.code !== "ERROR") {
            $("#saveDiv").show();
        }
        else
            alert(data.text);
    });
}

function removeTitleFromCollection(sku, catalogID) {
    $("#in_" + catalogID).remove();
    $("#collectionList").addClass("progressBackground");

    $("#inCollections_" + sku).html($(".collectionIn").length);
    updateActionMenuCollectionCount(sku);

    var collectionValues = {
        sku: sku,
        catalogID: catalogID
    };
    $.post("/getJSONData.aspx?builder=RemoveTitleFromCollection", collectionValues, function (data) {
        if (data.code == "SUCCESS") {
            $.url = "/GetTreelineControl.aspx?controlName=/uc/catalog/collectionManagement_Available.ascx&collectionID=" + catalogID + "&sku=" + sku;
            $("#tempContainer").load($.url, function () {
                $("#collectionList").append($("#tempContainer").html());
                $("#collectionList").removeClass("progressBackground");
                $("#tempContainer").html("");

                var sortName = "name";
                if ($("#sortBy_updatedDate").hasClass("selectedCollectionSort")) {
                    sortName = "updatedDate";
                }
                sortCollections(sku, sortName, 'collectionList');
            })
        }
    }, "json");
}

function addSelectedTitlesToCollection(catalogID) {
    var skuList = getSelectedItems();
    skuList = skuList.join(",");
    addTitlesToCollection(skuList, catalogID, true)
}

function addTitlesToCollection(skuList, catalogID, selected) {
    var catalogName;
    if ($("#collection_" + catalogID).length > 0) {
        catalogName = $("#collection_" + catalogID).attr("data-name");
    } else {
        catalogName = $("#newCollectionName").val();
    }

    $("#collection_" + catalogID).remove();
    $("#collectionListIn").addClass("progressBackground");
    var collectionValues = {
        skuList: skuList,
        catalogID: catalogID,
        sourceMailingID: window.getListViewProperty("selectedMailingID")
    };
    $.post("/getJSONData.aspx?builder=AddTitleToCollection", collectionValues, function (data) {
        if (data.code == "SUCCESS") {
            $.url = "/GetTreelineControl.aspx?controlName=/uc/catalog/collectionManagement_In.ascx&collectionID=" + catalogID + "&sku=" + skuList;
            $("#tempContainer").load($.url, function () {
                if (!selected) {
                    $("#collectionListIn").append($("#tempContainer").html());
                    $("#tempContainer").html("");
                    $("#collectionListIn").removeClass("progressBackground");

                    sortCollections(skuList, 'name', 'collectionListIn');

                    $("#inCollections_" + skuList).html($(".collectionIn").length);
                    updateActionMenuCollectionCount(skuList);
                } else {
                    $("#collectionListAdded").append("<div style='clear: both;'><div style='cursor: pointer;' class='column icon-note-icon' title='" + getRes("edit_collection") + "' onclick='javascript:closeModal(); AddEditCatalog(" + catalogID + ", 0)'></div>"
                        + "<a class='black-colored-link columnSpaced' title='" + getRes("open_collection")
                        + "' onclick='onOpenEditedCollectionClick(" + catalogID + ")'>" + catalogName + "</a>"
                        + "<div style='clear: both;'></div></div>");
                    $("#collection_" + catalogID).remove();
                }
            })
        }
    }, "json");
}

function onOpenEditedCollectionClick(catalogID) {
    if (getListViewProperty("dashboardType") == getEnumValue("dashType", "LA_HOME") && $("#availableBranches").length > 0) {
        var analysisStoreId = $("#availableBranches").attr("val");
        $.get("api/v1/store/" + analysisStoreId, function (store) {
            savePreference(ePlus.user.userPrefType.markup, ePlus.user.userPrefName.selectedAccount, store.orgID, function () {
                window.location.href = "/#catalogID=" + catalogID;
            });
        });
    } else {
        window.location.href = "/#catalogID=" + catalogID;
    }
};

function updateActionMenuCollectionCount(sku) {
    var currentCount = $(".collectionIn").length;
    $('.myCollections_' + sku, $("#itemContainer")).html(currentCount);
    if (currentCount === 0) {
        $(".myCollections_" + sku, $("#itemContainer")).hide();
    } else {
        $(".myCollections_" + sku, $("#itemContainer")).show();
    }
}

function createNewCollection(sku, collectionName) {
    $("#collectionListIn").addClass("progressBackground");
    $("#addNewCollection").hide();
    var collectionValues = {
        collectionName: encodeURI(collectionName)
    };
    $.post("/getJSONData.aspx?builder=CreateNewUserCollection", collectionValues, function (data) {
        if (data.code == "SUCCESS") {
            if (sku == "All") {
                addSelectedTitlesToCollection(data.data);
            } else {
                addTitlesToCollection(sku, data.data);
            }
            $("#addNewCollection").show();
            $("#newCollectionName").val("");
        } else {
            alert(data.text);

            if (data.code === 'ANONYMOUS') {
                loginAndReturnToCurrentPage();
            }
        }
    }, "json");
}

function sortByName(collectionList) {
    return _.sortBy(collectionList, function (element) {
        return $(element).data("name");
    });
}

function sortCollections(sku, sortBy, controlID) {
    if (controlID === 'collectionList') {
        $(".sortBySection", $("#sortOptions")).removeClass("selectedCollectionSort");
        $("#sortBy_" + sortBy).addClass("selectedCollectionSort");
    }

    if (_.isEmpty(sku))
        return;

    var collectionList = $('#' + controlID).children();
    collectionList = getSortedCollectionList(collectionList, sortBy);

    var html = "";
    _.forEach(collectionList, function (element) {
        html += element.outerHTML;
    });

    if (html !== "") {
        $('#' + controlID).html(html);
    }
}

function getSortedCollectionList(collectionList, sortBy) {
    if (sortBy == 'updatedDate') {
        return _.sortBy(collectionList, function (element) {
            var date = $(element).data("updated");
            if (!_.isNil(date)) {
                return new Date(date);
            }

            return new Date();
        }).reverse();
    } else {
        return _.sortBy(collectionList, function (element) {
            return $(element).data("name");
        });
    }
}

function clickNewCollection(sku) {
    var collectionName = $("#newCollectionName").val();
    if (collectionName != "") {
        createNewCollection(sku, collectionName);
    }
}

function deleteUserFolderOrTag(tags, tagOwnership, tagObject) {
    $.post("/getJSONData.aspx?builder=DeleteUserTags", { tags: encodeURI(tags), ownorall: tagOwnership, tagObject: tagObject },
        function (data) {
            if ("ERROR" == data.code) {
                alert(data.text);
            }
            else {
            }
        }, "json");
}

function deleteFolderFromHeader() {
    var folderName = $("#selectedOptionText").html();
    $.post("/getJSONData.aspx?builder=DeleteUserFolder",
        { folder: folderName },
        function (data) {
            if ("ERROR" == data.code) {
                alert(data.text);
            } else {
                $("#deleteFolder").webuiPopover('destroy');
                reloadCurrentPage();
            }
        },
        "json");
}

function renameFolderFromHeader(tagObject) {
    var newFolderName = $("#newFolderName").val();
    var oldFolderName = $("#selectedOptionText").html();
    renameUserFolderOrTag(oldFolderName, newFolderName, 0, tagObject);
}

function renameUserFolderOrTag(oldTagName, newTagName, tagOwnership, tagObject) {
    if (newTagName != "" && oldTagName != newTagName) {
        $.post("/getJSONData.aspx?builder=RenameUserTag",
            { tag: oldTagName, newname: newTagName, ownorall: tagOwnership, tagObject: tagObject, savePreference: true },
            function (data) {
                if ("ERROR" == data.code) {
                    alert(data.text);
                } else {
                    $("#renameFolder").webuiPopover('destroy');
                    reloadCurrentPage();
                }
            },
            "json");
    }
}

function revertOrderLink() {
    $('#shareWithDiv').show();
    $('#emailShareDiv').hide();
    $("#orderEmailBox").html("");
}

function ClearPlaceholder(inputPopulate) {
    var elem = $("#" + inputPopulate);

    if (elem && elem.hasClass("hasPlaceholder")) {
        elem.val("");
        elem.removeClass("hasPlaceholder");
        elem.css("color", "#969EA4");
    }
}

function CheckForEscape(event) {
    if (event.which == 27 || event.keyCode == 27) {
        if (event.preventDefault)
            event.preventDefault();
        else
            event.returnValue = false;

        $("#ac_results").hide();
    }
}

function CheckPlaceholder(inputPopulate, defaultText) {
    var elem = $("#" + inputPopulate);

    if (elem.length > 0 && elem.val().length == 0 && !elem.hasClass("hasPlaceholder")) {
        elem.css("color", "#969EA4");
        elem.addClass("hasPlaceholder");
        elem.val(defaultText);
    }
}

function showCoverRibbonOverlayOnImageLoad(_this) {
    $(_this).siblings(".cover-ribbon-overlay").css("display", "block");
}

function formatCurrency(amount, cultureCode, options, callback) {
    if (typeof Intl === 'undefined') {
        formatCurrencyForOldBrowser(amount, callback);
        return;
    }
    cultureCode = cultureCode || 'en-US';

    options = $.extend({}, {
        style: 'currency',
        currency: 'USD',
        currencyDisplay: 'symbol'
    }, options);

    var total = new Intl.NumberFormat(cultureCode, options).format(amount);

    if (typeof callback === 'function') {
        callback(total);
    }
}

function formatCurrencyForOldBrowser(amount, callback) {
    $.getJSON("/getJSONData.aspx?builder=JsCurrencyConvert&number=" + amount, function (data) {
        if (typeof callback === 'function' && data) {
            callback(data.text);
        }
    });
}

function savePublisherPriority(publisherOrgId, priority, onSuccess, onError) {
    var values = {
        orgId: publisherOrgId,
        prioritySection: priority
    };

    $.post("/getJSONData.aspx?builder=SavePriorityChange", values, function (data) {
        if (data.code === "ERROR") {
            alert(data.text);
            if (typeof onError === "function") {
                onError();
            }
        } else if (typeof onSuccess === "function") {
            onSuccess();
        }
    }, "json");
}

function updateSortRefineNoteMarkup(sku, noteLength, maxNoteLength) {
    if (isSortRefineInitialized()) {
        window.sortrefine[window.items.indexOf(sku)].Note_Markup = noteLength < maxNoteLength ? 0 : 1;
    }
}

function createAndShowMaintenanceAlert(maintenanceMessage, maintenanceId) {
    var alertId = "maintenance-alert";
    var closeEvent = function () {
        if (maintenanceId) {
            savePreference(window.ePlus.user.userPrefType.display, maintenanceId, "true");
        }
    };
    $('body').prepend(templateCache.dismissableWarningAlert(
        {
            id: alertId,
            message: maintenanceMessage
        }
    ));
    $("#" + alertId).fadeToggle("slow", "linear");
    initAlertCloseEvent(alertId, closeEvent);
}

function initAlertCloseEvent(id, closeCallback) {
    $("#" + id + " .close").click(function (event) {
        event.preventDefault();
        $("#" + id).fadeToggle("slow", "linear");
        if (typeof closeCallback === "function") {
            closeCallback();
        }
    });
}

function getSelectedAccountOrgId() {
    return getListViewProperty("selectedOrgID");
}

function openExportPreviewHeader(header) {
    var $header = $('#popover_' + header);
    var isVisible = $header.css("display") !== "none";
    if (isVisible) {
        $header.slideUp();
    } else {
        $('.pop_menu').slideUp();
        $header.slideDown();
    }
}

function getCurrentPageNumber() {
    return parseInt($("#currentPage").val(), 10) || 1;
}

function getMoreRecentDateString(dateString1, dateString2) {
    if (_.isEmpty(dateString1) && _.isEmpty(dateString2)) {
        return "";
    } else if (!_.isEmpty(dateString1) && !_.isEmpty(dateString2)) {
        if (new Date(dateString1).getTime() > new Date(dateString2).getTime()) {
            return dateString1;
        } else {
            return dateString2;
        }
    } else {
        return !_.isEmpty(dateString1) ? dateString1 : dateString2;
    }
}

function logPageHit(siteContext, siteArea, sessionId) {
    var usageEventDto = {
        siteContext: siteContext,
        siteArea: siteArea,
        sessionId: sessionId
    };
    $.ajax({
        type: "PUT",
        cache: false,
        url: "api/v1/usage",
        contentType: "application/json",
        data: JSON.stringify(usageEventDto)
    });
}

function sanitize(s) {
    return filterXSS(s);
}

function stripHtml(s) {
    return filterXSS(s, {
        whiteList: {},
        stripIgnoreTag: true,
        stripIgnoreTagBody: ['script'],
    });
}

function handleDrcWidgetOverlayClick(sku, descriptions) {
    loadModalTitle(sku, 'dash', descriptions)
}

function handleDrcWidgetRemoveButtonClick(sku, laneId, resultType, dashType, widgetId) {
    hideItemInDashboard(sku, laneId, resultType, dashType.toString(), widgetId)
}

function renderDrcQuickSendModalAnchor(sku) {
    function renderQuickSend() {
        var drcQuickSendRoot = document.getElementById("drcQuickSendAnchor_" + sku);
        if (drcQuickSendRoot) {
            var DrcQuickSendAnchor = EdelweissComponents.Drc.DrcQuickSendModalAnchor;
            DrcQuickSendAnchor.render(drcQuickSendRoot, { sku: sku });
        }
    }
    EdelweissComponentsLoader.handleRender(renderQuickSend);
}

function openShowcaseListView(showcaseCatalogId, originalCatalogId, sku) {
    var pageHash = '#catalogID=' + showcaseCatalogId + '&previousCatalogId=' + originalCatalogId + '&previousSku=' + sku;

    pageChange(pageHash);
}

function backToPreviousCatalog(catalogId, sku) {
    window.listViewFinishedLoadingCallback = function () {
        jumpToAndHighlightItem(sku);
    };

    pageChange('#catalogID=' + catalogId);
}

function renderProductSalesRightsModalAnchor(sku) {
    function renderProductSalesRights() {
        var $productSalesRightsRoot = $(".salesRightsLink_" + sku);

        $productSalesRightsRoot.each(function () {
            var ProductSalesRightsAnchor = EdelweissComponents.Product.ProductSalesRightsModalAnchor;
            ProductSalesRightsAnchor.render($(this)[0], { label: $productSalesRightsRoot.data('label'), sku: sku });
        });
    }

    EdelweissComponentsLoader.handleRender(renderProductSalesRights);
}

function showHideGainsightKnowledgeBot(action) {
    if (window.aptrinsic) {
        window.aptrinsic('kcb', action);
    }
}

function isNewListViewEnabled() {
  var e = document.getElementById('newListViewToggle');
  return !!(e && e.checked);
};
/**
 * Safely get list view property.
 * @param {String} propName window.listView property name
 * @returns {Any} Property value (if exists)
 */
function getListViewProperty(propName) {
    if (window.listView != null) {
        return window.listView[propName];
    }
    return null;
}

/**
 * Safely set list view property, if window.listView exists.
 * @param {String} propName window.listView property name
 * @param {Any} value
 * @returns {boolean} Result of set operation.
 */
function setListViewProperty(propName, value) {
    if (window.listView != null) {
        window.listView[propName] = value;
        return true;
    }
    return false;
}

/**
 * Determine if page state reflects a listView page.
 * @returns {boolean}
 */
function isListView() {
    return window.listView != null;
}

/**
 * Gets a list of item ids for the current list view after refinements
 * @returns {string[]} - Array of item ids
 */
function getRefinedItemIds() {
    return _.map(window.rows, function (row) {
        return row.item;
    });
}

function buildRefinementOptions(dashType, resultType, itemType) {
    var result = {
        dashType: dashType,
        resultType: resultType,
        itemType: itemType,
        items: window.items
    };

    switch (resultType) {
        case getEnumValue('resultType', 'TITLE_CATALOG'):
            result.catalogId = window.catalogID;
            break;
        case getEnumValue('resultType', 'TITLEPUBLICITYCAMPAIGN'):
            result.catalogId = getHashValue('campaignCatalogID');
            result.campaignId = getHashValue('campaignID');
            break;
        case getEnumValue('resultType', 'IMPRINTGROUPADMINISTRATION'):
            result.pubOrgId = getHashValue('publisher');
            break;
    }

    return result;
}

// This populates the refinement section via an API
function createRefinements(dashType, resultType, itemType, always) {
    var data = buildRefinementOptions(dashType, resultType, itemType);
    var postParams = JSON.stringify(window.items);

    if (_.isString(postParams)) {
        $.ajax({
            type: 'POST',
            url: 'api/me/refinements',
            contentType: 'application/json',
            data: JSON.stringify(data),
            cache: false
        }).done(function (data) {
            if (data) {
                if ("refineMap" in data) {
                    window.refineMap = data.refineMap;
                }
                if ("combinedRefineMap" in data) {
                    window.combinedRefineMap = data.combinedRefineMap;
                }
                if ("refinementTrees" in data) {
                    window.refinementTrees = data.refinementTrees;
                }
                if ("sortrefine" in data) {
                    window.sortrefine = data.sortrefine;
                }
                if ("autoCompleteJson" in data) {
                    window.autoCompleteJson = data.autoCompleteJson;
                }
                if ("headerCountMap" in data) {
                    var headerCounts = data.headerCountMap;
                    for (var key in headerCounts) {
                        $("#qhead" + key).html(headerCounts[key]);
                        window.showHide_qhead(key);
                    }
                }
            }

            window.selected = 0;
            populateRefineDetail();
            initializeAutoComplete();
        }).fail(function () {
            $(".refineArea").hide();
            $(".listSort").hide();
            $("#autoComplete").hide();
        }).always(function () {
            if (!_.isNil(window.orderBackbone) && o.utils.getUnitsViewType() === "order") {
                o.getOrderTotals();
            }
            if (typeof always === "function") {
                always();
            }
        });
    }
}

function ListViewPostProcessing_Item_People(userID) {
    populateFixedJackets_People(82, userID);
}

/**
 * Refreshes the list view top menu bar
 */
function refreshListViewTopMenus() {
    getOtherActionsWebuiPopover();
    _.forEach([
        refreshCatalogSectionsMenu,
        refreshCatalogShowcasesMenu
    ], function (func) {
        if (_.isFunction(func)) {
            func();
        }
    });
}

function initializeFiltersVariable(section) {
    $('.filter_' + section, '#leftNavInterior').off('click').on('click', function () {
        var $self = $(this);

        $(window).trigger('interaction', [function () {
            $(".ltRow, .rContent").addClass("progressBackground");
            var controlId = $self.attr('data-section');
            changeFilterVariable(section, controlId);
        }]);
    });

    $('.nofilterRow_' + section).off('click').on('click', function () {
        $(window).trigger('interaction', [function () {
            clearFilters(section);
            updateRefineFilterCounts();
        }]);
    });
}

function changeFilterVariableOnAnalyticsHome(section, controlId) {
    buildFilterOptions(section);

    var doAddToAttributeFilters = $("#f_" + controlId).hasClass("box_checked");
    var filterType = parseInt(section);
    var finalRefinement = $("#fD_" + controlId).attr("data-key");
    var fullRefinement = ePlus.modules.listView.refinements.isMultiValueRefinement(filterType) ?
        $("#" + controlId).attr("data-attr") : finalRefinement;

    var tempCategoryFilter = window.EdelweissAnalytics.getCurrentTemporaryCategoryFilterObject();
    var tempCategoryFilterNameToRemove = null;
    if (!doAddToAttributeFilters && window.EdelweissAnalytics.isLeftNavCategoryFilterType(filterType)
        && !_.isEmpty(tempCategoryFilter) && tempCategoryFilter.categoryName === finalRefinement) {
        tempCategoryFilterNameToRemove = tempCategoryFilter.categoryName;
        window.EdelweissAnalytics.removeTemporaryCategoryFilter();
    }

    var visibleDashTypes = window.EdelweissAnalytics.getDashTypesOfVisibleLanes();
    visibleDashTypes.forEach(function (dashType) {
        if (window.EdelweissAnalytics.laneKeyByDashType.hasOwnProperty(dashType)) {
            var laneKey = window.EdelweissAnalytics.laneKeyByDashType[dashType];
            if (tempCategoryFilterNameToRemove !== null) {
                window.EdelweissAnalytics.removeAttributeFilterFromFilterOptions(dashType,
                    tempCategoryFilterNameToRemove);
            } else {
                window.EdelweissAnalytics.updateAttributeFiltersWithRefinement(dashType, section,
                    fullRefinement, doAddToAttributeFilters);
            }
            if (laneKey === window.EdelweissAnalytics.LaneKeys.TrendsAnalysis) {
                window.EdelweissAnalytics.isTrendsAnalysisChartUpdated = true;
            }
            window.EdelweissAnalytics.startLaneUpdateProcess(laneKey);
        }
    });
}

function changeFilterVariable(section, controlId) {
    toggleAllRefinementOptionsCheckbox(section, controlId);
    $("#f_" + controlId).toggleClass("box_checked box_unchecked");

    if (window.getEnumValue("dashType", "LA_HOME") === parseInt(getListViewProperty("dashboardType"))) {
        changeFilterVariableOnAnalyticsHome(section, controlId);
        return;
    }

    buildFilterOptions(section);
    applyFilters();
    refreshListViewTopMenus();
    renderPaging(getPageCount());
    togglePagingSection();

    if (getEnumValue("listType", "ANALYSISOFTITLESET") === parseInt(getListViewProperty("listType"))) {
        EdelweissAnalytics.startLaneUpdateProcess(EdelweissAnalytics.LaneKeys.TitleSetStockAnalysis);
    }
}

function toggleAllRefinementOptionsCheckbox(section, controlId) {
    if ($('#f_' + controlId).hasClass("box_checked") && !$('#f_' + controlId).parent().hasClass("menuOption")) {
        $("#refineHeaderCheckbox" + section).removeClass("box_checked").addClass("box_unchecked");
    } else {
        var refinementOptionCount = $("div.filterOption:not(.menuOption) > .filter_" + section + ":visible").length;
        var checkedRefinementOptionCount = $("div.filterOption:not(.menuOption) > .box_checked.filter_" + section + ":visible").length;

        if (refinementOptionCount === checkedRefinementOptionCount + 1) {
            $("#refineHeaderCheckbox" + section).removeClass("box_unchecked").addClass("box_checked");
        }
    }
}

function buildFilterOptions(section) {
    if ($(".filter_" + section, $("#refine" + section)).hasClass("box_checked")) {
        //Previousy Selected As Filter, Now Unchecked
        $(".nofilter_" + section).removeClass("box_checked").addClass("box_unchecked");

        //Add indicator to each row to hide
        $('.tlList').addClass(section + "_hide");
        $("#fD_" + section).html("");
        $(".filterRow_" + section, $("#refine" + section)).each(function (i, obj) {
            var fId = $(this).attr('id');
            var fAttr = $(this).attr('data-attr');
            if ($("#f_" + fId).hasClass("box_checked")) {
                var runningString = '<div class="column">';
                if ($("#fD_" + section).html() != '') {
                    runningString = '<div class="stripItem stripSeparator">&nbsp;</div>';
                }
                runningString += '<div class="column icon-remove-icon filterRemove activeFilter filterRemove_' + section + '" data-attr="' + fAttr + '" data-type="' + section + '" onclick=\'javascript:changeFilterVariable("' + section + '","' + fId + '");\'></div><div class="column filterOptText">' + $("#fD_" + fId).html() + '</div></div></div>';
                $("#fD_" + section).html($("#fD_" + section).html() + runningString);
            }
        });
    } else {
        $("#fD_" + section).html("");
    }

    getFilterCount();
    showHideFilterRow(section);
}

function showHideFilterRow(section) {
    if ($("#fD_" + section).html() == "") {
        $("#fRow_" + section).hide();
    } else {
        $("#fRow_" + section).show();
    }
}

function getFilterCount() {
    var numFilters = $(".filterOptText").length;
    $("#fsSumCount").html(numFilters);
    if (numFilters > 0) {
        $("#fsSum").show();
    } else {
        $("#fsSum").hide();
    }
}

function applyFiltersWithoutScrolling(startPos, callback) {
    applyFilterTag();
    displayRelevantRowsWithoutScrolling(typeof startPos === 'undefined' ? 0 : startPos, callback);
}

function applyFilters(startPos, callback) {
    applyFilterTag();
    displayRelevantRows(typeof startPos === 'undefined' ? 0 : startPos, callback);
}

function isFilterApplied(filterType) {
    return $("#fRow_" + filterType).is(":visible");
}

function clearAllFiltersOnAnalyticsHome() {
    ePlus.modules.leftNav.clearRefinementsOnAnalyticsHome();
    clearFilterUi();
    getFilterCount();
}

function clearAllFilters() {
    WebuiPopovers.hideAll();
    $(window).trigger('interaction', [function () {
        $(".ltRow, .rContent").addClass("progressBackground");
        if (getEnumValue("dashType", "LA_HOME") === parseInt(getListViewProperty("dashboardType"))) {
            clearAllFiltersOnAnalyticsHome();
            return;
        }

        clearFilterUi();
        window.scrollTo(0, 0);
        applyFilterTag();
        if (getEnumValue("listType", "ANALYSISOFTITLESET") === parseInt(getListViewProperty("listType"))) {
            EdelweissAnalytics.startLaneUpdateProcess(EdelweissAnalytics.LaneKeys.TitleSetStockAnalysis);
        } else {
            displayRelevantRows(0);
        }
        getFilterCount();
    }]);
}

function clearFilterUi() {
    $(".filter").removeClass("box_checked").addClass("box_unchecked").removeClass("filteredOut");
    $(".selectedfD").html("");
    $(".fRow").hide();
}

function filterClearCheck() {
    $("#results").html("");
    $("#fsSum").hide();
}

function clearFilters(section) {
    WebuiPopovers.hideAll();
    $(".ltRow, .rContent").addClass("progressBackground");
    if (isFilterApplied(section)) {
        if (getEnumValue("dashType", "LA_HOME") === parseInt(getListViewProperty("dashboardType"))) {
            ePlus.modules.leftNav.clearRefinementsOnAnalyticsHome(section);
        }
        if (!$(".nofilter_" + section).hasClass("box_checked")) {
            $(".nofilter_" + section).addClass("box_checked").removeClass("box_unchecked");
            $(".filter_" + section).removeClass("box_checked").addClass("box_unchecked").removeClass("filteredOut");
            $('.tlList').removeClass(section + "_hide");
        }
        $("#fD_" + section).html("");
        showHideFilterRow(section);
        if (getListViewProperty("itemMode") == 1) {
            $(".tr_Frame").show();
            $("#listOverviewContainer").show();
            setListViewProperty("itemMode", 0);
            $(".list_header").show();
            $("#singleTitleHeader").hide();
        }
        window.scrollTo(0, 0);
        if (getEnumValue("dashType", "LA_HOME") !== parseInt(getListViewProperty("dashboardType"))) {
            applyFilterTag();
            displayRelevantRows(0);
        }
    }
    getFilterCount();
}


function PopulateActionMenuReviewsShelves(sku, sequence) {
    var itemIndex = -1;
    if (window.items) {
        itemIndex = window.items.indexOf(sku);
    }
    if (itemIndex > -1 && window.sortrefine) {
        if (itemIndex in window.sortrefine) {
            if (window.sortrefine[itemIndex].PeerReviews > 0) {
                var reviews = window.sortrefine[itemIndex].PeerReviews;
                $("#comReview_" + sku).html(reviews).show();
                $("#myR_" + sku).show();
            } else {
                $("#myR_" + sku).hide();
            }
            if (window.sortrefine[itemIndex].PeerShelves > 0) {
                var shelves = window.sortrefine[itemIndex].PeerShelves;
                $("#comShelf_" + sku).html(shelves).show();
                $("#myS_" + sku).show();
            } else {
                $("#myS_" + sku).hide();
            }
        }
    } else {
        setTimeout(function () {
            if (window.communityRecursion < 100) {
                PopulateActionMenuReviewsShelves(sku, sequence);
                window.communityRecursion += 1;
            }
        }, 1000);
    }
}

function updateSkuModalProductView(sku) {
    updateProductViews('#product-view-' + sku + '-modal');
}

function updateProductViews(selector) {
    var $productViews = selector ? $(selector) : $('.product-view');

    $productViews.each(function () {
        $('ul.hidden', $(this)).each(function () {
            var $productViewElements = $(this);
            var visibleProductViewElementsContent =
                $('li:not(.hidden)', $productViewElements)
                    .map(function () {
                        return $(this).html();
                    })
                    .get();
            var visibleProductViewElementsContentList = visibleProductViewElementsContent.join(' | ');

            // Put the visible content list in the div after the ul
            $productViewElements.next().html(visibleProductViewElementsContentList);
        });
    });
}

function showSingleTitle(sku, itemNumber, e) {
    if (e) {
        e.stopPropagation();
    }
    if (EdelweissAnalytics.isAnalyticsUser) {
        loadModalTitle(sku, 'dash', itemNumber);
    }
    $(window).trigger('interaction', [function () {
        $("#previousScrollPosition").val($(window).scrollTop());
        $("#listOverviewContainer").hide();
        setListViewProperty("itemMode", 1);
        $("#tempList2").html("");

        var resultType = getListViewProperty("resultType"),
            listType = getEnumValue('listType', 'LEGACY');

        renderRelevantTitleRows(itemNumber, 1, 1, null, resultType, null, listType);
        if ($("#backToFirst").css("display") != "none") {
            $("#backToFirst").hide();
        }
        $(".list_header").hide();

        $("#singleTitle").html(itemNumber * 1 + 1);
        $("#singleTitleOf").html(window.rows.length);
        $("#singleTitleHeader").show();
        window.scrollTo(0, 0);
        $("#pagingRow").hide();
        $("#page-next").addClass('hidden');
        $("#page-prev").addClass('hidden');
    }]);
}

function revertFromSingleTitle() {
    $("#listOverviewContainer").show();
    setListViewProperty("itemMode", 0);
    $(".list_header").show();
    $("#singleTitleHeader").hide();
    $("#pagingRow").show();
    var currentTitle = $("#singleTitle").html() * 1;
    displayRelevantRows(50 * Math.floor((currentTitle - 1) / 50), currentTitle);
}

function nextTitleClick(increment) {
    var currentTitle = $("#singleTitle").html() * 1;
    var nextItemNumber = currentTitle + increment;
    var itemCount = window.rows.length;

    if (nextItemNumber == itemCount + 1) {
        nextItemNumber = 1;
    }
    if (nextItemNumber < 1) {
        nextItemNumber = itemCount;
    }
    var nextSku = window.rows[nextItemNumber];

    $("#singleTitle").html(nextItemNumber);

    var resultType = getListViewProperty("resultType"),
        listType = getEnumValue('listType', 'LEGACY');

    renderRelevantTitleRows(nextItemNumber - 1, 1, 1, null, resultType, null, listType);
}

function nextTitleModal(sku, increment) {
    var currentTitle = _.findIndex(window.sortrefine, ['item', sku]);
    var nextItemNumber = currentTitle + increment;
    var itemCount = window.rows.length - 1;

    if (nextItemNumber > itemCount) {
        nextItemNumber = 0;
    } else if (nextItemNumber < 0) {
        nextItemNumber = itemCount;
    }

    var nextSku = window.sortrefine[nextItemNumber].item;
    var url = "/GetTreelineControl.aspx?controlName=/uc/product/skuModal.ascx&source=modal&sku=" + nextSku;

    if ($("#selectedDiv").length > 0) {
        url += "&startingContent=" + $("#selectedDiv").attr("data-content-type");
    }

    var currentPosition = nextItemNumber + 1;
    $("#modalNavigationCurrentPosition").html(currentPosition);

    $("#popModal_prevNavigation").off('click');
    $("#popModal_nextNavigation").off('click');

    $("#popModal_inner").load(url, function () {
        $("#popModal_prevNavigation").on('click', nextTitleModal.bind(null, nextSku, -1));
        $("#popModal_nextNavigation").on('click', nextTitleModal.bind(null, nextSku, 1));
    });

    return false;
}

function loadFooterBar() {
    var querystring = "&resultType=" + getListViewProperty("resultType") + "&dashboardType=" + getListViewProperty("dashboardType") +
        "&itemID=" + getListViewProperty("itemID") + "&mailingID=" + getListViewProperty("selectedMailingID") + "&selectedOrderID=" + getListViewProperty("selectedOrderID");
    $.url = "/GetTreelineControl.aspx?controlName=/uc/listviews/ListView_Footer.ascx" + querystring;
    $("#footerBar").load($.url);
}

function showRefineSection(filterType) {
    if (isRefineHidden(filterType)) return;
    $("#refineFilter" + filterType).show();
}

function isRefineHidden(filterType) {
    return $("#homeOptionArrow" + filterType).hasClass("icon-drop-down-icon");
}

function hideRefineSection(filterType) {
    $("#refineFilter" + filterType).hide();
}

function changeSort(sortOrd) {
    setListViewActiveSort(sortOrd);
    setSortOrd(sortOrd);
    setSortOrdIcon(sortOrd);
    saveSortPreferences();
    sortList(sortOrd);
    applyFilters();
}

function setListViewActiveSort(sortOrd) {
    var sortOrder = {
        sortOrder: sortOrd,
        sortDir: getSortDir()
    }
    window.activeSortList[getListViewProperty("dashboardType")] = sortOrder;
}

function sortList(sortType) {
    var sortClass = convertSortTypesToNames(sortType * 1);
    var sortDir = parseInt(getSortDir());

    var ASCENDING_SORT = 0;
    window.sortrefine.sort(function (a, b) {
        if (sortDir === ASCENDING_SORT) {
            return parseFloat(a[sortClass]) - parseFloat(b[sortClass]);
        } else {
            return parseFloat(b[sortClass]) - parseFloat(a[sortClass]);
        }
    });

    window.rows = getSortedRows();
    window.items = window.sortrefine.map(function (sortrefine) {
        return sortrefine.item;
    });
    updateListViewTopMenu();
}

function selectSelectedRows(accumulatedSelectedRows, row) {
    var SELECTED_ROW = 1;
    if (row.selected === SELECTED_ROW) {
        accumulatedSelectedRows.push(row.item);
    }

    return accumulatedSelectedRows;
}

function getSortedRows() {
    var UNSELECTED_ROW = 0;
    var SELECTED_ROW = 1;
    var selectedRows = window.rows.reduce(selectSelectedRows, []);

    return window.sortrefine.map(function (sortrefine) {
        return {
            item: sortrefine.item,
            selected: selectedRows.indexOf(sortrefine.item) > -1 ? SELECTED_ROW : UNSELECTED_ROW
        }
    });
}

function updateListViewTopMenu() {
    rebuildCatalogSectionsMenu();
    rebuildCatalogShowcasesMenu();
}

function setSortOrdIcon(sortOrd) {
    $('.sortoption').addClass("box_unchecked").removeClass("box_checked");
    $('#sort_' + sortOrd).addClass("box_checked");
}

function setSortDirIcon(sortDir) {
    $("#ascDesc")
        .toggleClass("icon-navi-up-icon", sortDir == 0)
        .toggleClass("icon-navi-down-icon", sortDir == 1);
}

function changeAscDesc() {
    var sortDir = getSortDir() == 0 ? 1 : 0;
    var sortInfo = {
        sortDir: sortDir,
        sortOrder: getSortOrd()
    };

    window.activeSortList[getListViewProperty("dashboardType")] = sortInfo;
    setSortDirIcon(sortDir);
    setSortDir(sortDir);
    saveSortPreferences();

    sortList(getSortOrd());
    applyFilters();
}

function setSortDir(value) {
    setListViewProperty("sortDir", value);
}

function padNumber(value, size) {
    if (_.isNil(value) || _.isNaN(value)) return value;
    var s = value + '';
    while (s.length < size) s = '0' + s;
    return s;
}

function getSortOrd() {
    return !_.isNil(window.listView) ? getListViewProperty("sortOrd") : 0;
}

function getSortDir() {
    return !_.isNil(window.listView) ? getListViewProperty("sortDir") : 0;
}

function areCatalogSubheadersHidden() {
    return $("#hideCatalogSubheaders").length > 0;
}

function hideCatalogSubheaders() {
    if (!areCatalogSubheadersHidden()) {
        $("<style />", {
            "id": "hideCatalogSubheaders",
            "type": "text/css",
            "html": "#listContent .catalogSubheader { display: none !important; }"
        }).appendTo(document.head);
    }

    refreshCatalogSectionsMenu();
}

function showCatalogSubheaders() {
    $("#hideCatalogSubheaders").remove();
    refreshCatalogSectionsMenu();
}

function initDrcActions(sku) {
    var $drcAreaTarget = $("#drcArea_" + sku);
    if (window.drcs && sku in window.drcs) {
        var drcDetailHtml = $("#drcDetail_" + sku).html();
        var $drcWebReader = $('#drcWebReader_' + sku);
        var isMobile = window.innerWidth < 700 || screen.width < 700 || document.documentElement.clientWidth < 700
        var isTablet = window.innerWidth < 1024 || screen.width < 1024 || document.documentElement.clientWidth < 1024

        var triggerType = 'hover';
        if (isMobile || isTablet) {
            WebuiPopovers.hideAll()
        } else {

            if ($drcWebReader.length) {
                drcDetailHtml += getGalleyEventPopoverHtml(null, $drcWebReader.html(), '');
            }

            if (!_.isNil(drcDetailHtml) && drcDetailHtml !== "") {
                $drcAreaTarget.webuiPopover({ content: drcDetailHtml, trigger: triggerType, container: '#as_' + sku, placement: 'left' });
            }
        }

    } else {
        $drcAreaTarget.hide();
    }
}

function getGalleyEventPopoverHtml(onClickHtml, text, drcDetailHtml) {
    var html = '';
    if (drcDetailHtml !== '') {
        html += '<hr>';
    }

    if (typeof onClickHtml != 'undefined') {
        html += '<span class="overallStyles clickable center-content textLarge" onclick="' +
            onClickHtml +
            '">' + text + '</span>';
    } else {
        html += '<span class="overallStyles center-content textLarge">' + text + '</span>';
    }

    return html;
}

function GetResultOption(resultType, controlId, doOverrideHideOnZeroCount) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/dashboard/controls/Dashboard_BallValue.ascx&resultType=" + resultType;
    $("#value_" + resultType).load(url, function () {
        if ($("#value_" + resultType).html() * 1 == 0 && controlId != window.selectedOption && !doOverrideHideOnZeroCount) {
            $("#row_" + resultType).hide();
        } else {
            $("#row_" + resultType).show();
        }
    });
}

function jumpToAndHighlightItem(item) {
    jumpToItem(item, function () {
        var $elem = $("#as_" + item);
        if ($elem.length > 0) {
            highlightElementAnimation($elem);
        }
    });
}

function jumpToItem(item, callback) {
    WebuiPopovers.hideAll();

    var position = -1;
    for (var i = 0; i < window.rows.length; i++) {
        if (item == window.rows[i].item) {
            position = i;
        }
    }
    if (position > -1) {
        var $currentListItem = $(".listItem_" + position);

        if ($currentListItem.length) {
            jumpToEnd(position + 1, callback);
        } else {
            updatePageHash(position, 50);
            adjustPagingDisplay(Math.floor((position) / 50) + 1);
            displayRelevantRows(50 * Math.floor((position) / 50), position + 1, callback);
        }
    }
}

function jumpToPlace(place, doIgnoreRowOffset, isPageJump) {
    var currentItem = doIgnoreRowOffset ? place : place + 1;
    if (place > -1) {
        adjustPagingDisplay(Math.floor((place) / 50) + 1);
        displayRelevantRows(50 * Math.floor((place) / 50), isPageJump ? null : currentItem);
    }
}

function scrollToElement($elem) {
    if (!$elem.length) return;

    var elementTop = $elem.offset().top;
    var $listHeader = $("#listHeader");
    var listHeaderHeight = $listHeader.height();
    var $appHeader = $('.fh-fixedHeader');
    var appHeaderHeight = $appHeader.height();
    var scrollToPosition = elementTop - listHeaderHeight - appHeaderHeight;

    $(window).scrollTop(scrollToPosition);

    return scrollToPosition;
}

function jumpToEnd(currentItem, callback) {
    if (!_.isNil(currentItem)) {
        adjustPagingDisplay(Math.floor((currentItem * 1 - 1) / 50) + 1);

        var $currentListItem = $(".listItem_" + (currentItem * 1 - 1));

        // If the item is hidden, display it
        $currentListItem.show();

        // If we aren't in a grid view, we want the item's parent element
        if (getListViewProperty('listType') != getEnumValue('listType', 'GRIDVIEW')) {
            $currentListItem = $currentListItem.parent();
        }

        if ($currentListItem.length) {
            setListViewHeaderToFixed();
            setListViewProperty('jumpToPosition', scrollToElement($currentListItem));
        }
    }

    if (_.isFunction(callback)) {
        callback();
    }
}

function getAppliedFilters() {
    var result = {};

    $(".filter.box_checked").each(function () {
        var section,
            controlId = this.id ? this.id.replace("f_", "") : null;

        if (controlId) {
            var parts = controlId.split("_");
            section = parts.length === 2 ? parts[0] : null;
        }

        if (section && controlId) {
            result[section] = result[section] || [];
            result[section].push(controlId);
        }
    });

    return result;
}

function togglePublisherComps(id, sku, isPublisherCompsVisible) {
    var mailingId = getListViewProperty("selectedMailingID");
    if (!mailingId) {
        mailingId = getHashValue("mailingID");
    }
    $.url = "/GetTreelineControl.aspx?controlName=/uc/product/CompsBasic.ascx&sku=" + sku + "&idPrefix=" + id + "&mailingID=" + mailingId + (isPublisherCompsVisible !== undefined ? "&isPublisherCompsVisible=" + isPublisherCompsVisible : "");

    $('#' + id).load($.url);
}

function toggleCompSalesData() {
    if (window.hasPosData) {
        showCompSalesData();
    } else {
        hideCompSalesData();
    }
}

function hideCompSalesData() {
    $('.data-header', 'table.comp-table > thead').addClass('hidden');
    $('.comp-data', 'table.comp-table > tbody').addClass('hidden');
    $('.multi-comp', 'table.comp-table').addClass('hidden');
}

function showCompSalesData() {
    $('.data-header', 'table.comp-table > thead').removeClass('hidden');
    $('.comp-data', 'table.comp-table > tbody').removeClass('hidden');

    if (window.stores > 1) {
        $('.multi-comp', 'table.comp-table').removeClass('hidden');
    } else {
        $('.multi-comp', 'table.comp-table').addClass('hidden');
    }
}

function saveCatalogSubscription(catalogId, localizedBaselineDate) {
    var url = "/api/me/catalogs/" + catalogId + "/subscription?apiView=localizedDateTimes";

    if (localizedBaselineDate) {
        url += "&localizedBaselineDate=" + localizedBaselineDate;
    }

    $.ajax({
        url: url,
        type: "POST"
    }).done(function (data) {
        if (!data) {
            alert(getRes('error_unexpected'));
            return;
        }

        if (getListViewProperty("itemType") === 1) {
            var text = getRes('unsubscribe');

            $('#catSubIcon').attr('title', text).addClass('accFont');
            $('#catSubStatus').html(text).removeClass('subscribe').addClass('unsubscribe');
            $('#catSubDate').html(data.localizedUpdatedDate || data.localizedCreatedDate);
            $('#catLastViewedDate').html(data.localizedBaselineDate);
            $('#catSubDateRow').show();
            $('#catLastViewedDateRow').show();
        }
    }).fail(function () {
        alert(getRes('error_unexpected'));
    });

    return false;
}

function removeCatalogSubscription(catalogId) {
    $.ajax({
        url: "/api/me/catalogs/" + catalogId + "/subscription",
        type: "DELETE"
    }).done(function () {
        if (getListViewProperty("itemType") === 1) {
            var text = getRes('subscribe');

            $('#catSubIcon').attr('title', text).removeClass('accFont');
            $('#catSubStatus').html(text).removeClass('unsubscribe').addClass('subscribe');
            $('#catSubDateRow').hide();
            $('#catLastViewedDateRow').hide();
        }
    }).fail(function () {
        alert(getRes('error_unexpected'));
    });

    return false;
}

function createMultiStorePopover(skus, sku) {
    var skuList = skus.split(",");
    $.each(skuList, function (i, value) {
        $('#multiGrid_' + sku + '_' + value).webuiPopover({
            type: 'async',
            placement: 'left',
            cache: false,
            url: "/GetTreelineControl.aspx?controlName=/uc/product/CompsMultiDetailGrid.ascx&sku=" + value
        });
    });
}

function removeListViewItem(itemId) {
    //coerce to string -- if not already
    itemId = '' + itemId;
    var index = _.findIndex(window.sortrefine, { item: itemId });
    if (index > -1 && _.isArray(window.sortrefine)) {
        var originalOrder = window.sortrefine[index].originalOrder;
        window.sortrefine.splice(index, 1);
        for (var i = 0; i < window.sortrefine.length; i++) {
            if (window.sortrefine[i].originalOrder > originalOrder) {
                window.sortrefine[i].originalOrder--;
            }
        }
    }

    index = _.findIndex(window.rows, { item: itemId });
    if (index > -1 && _.isArray(window.rows)) {
        window.rows.splice(index, 1);
    }

    if (_.isArray(window.items)) {
        index = window.items.indexOf(itemId);
        if (index > -1) {
            window.items.splice(index, 1);
        }
    }
}

function setRefinementInProgress(isInProgress) {
    $('#leftNavInterior').toggleClass('progressBackground', isInProgress);
}

function updateRefineFilterCounts() {
    resetAllRefinementsReadyState();

    getFilterTypes().forEach(function (filterType) {
        calculateSectionTotalsVariable(filterType);
    });
}

function resetAllRefinementsReadyState() {
    $('.homeOptionRefineArea', $('#leftNavRefine'))
        .hide()
        .removeClass('done');
}

function setRefinementReadyState(filterType) {
    $('#refineFilter' + filterType)
        .show()
        .addClass('done');

    if (filterType != getEnumValue('filterType', 'ACCOUNTCOMBINEDFILTER')) return;

    for (var filterType in window.combinedRefineMap) {
        $('#refineFilter' + filterType)
            .show()
            .addClass('done');
    }
}

function getFilterTypes() {
    return $('.homeOptionRefineArea', $('#leftNavRefine')).map(function () {
        var filterType = $(this).data('filtertype');
        return filterType !== null && filterType !== undefined ? filterType : null;
    }).get();
}

function calculateSectionTotalsVariable(filterType) {
    setRefinementReadyState(filterType);

    if (areAllRefinementsReady()) {
        getRefineNumbers();
    }

    toggleNoRefinementsMessage();
}

function areAllRefinementsReady() {
    return $('.homeOptionRefineArea.done', $('#leftNavRefine')).length === $('.homeOptionRefineArea', $('#leftNavRefine')).length;
}

function toggleNoRefinementsMessage() {
    var visibleRefinementCount = $('.homeOptionRefineArea:visible', $('#leftNavRefine')).length;
    $("#noRefine").toggle(visibleRefinementCount === 0);
}

/**
 * @param {String} selector jQuery selector string, ".someClass"
 * @param {number} newCount
 */
function setCount(selector, newCount) {
    var $e = $(selector);
    if (_.isNumber(newCount) && !_.isNaN(newCount) && $e.length) {
        $e.text(newCount.toLocaleString());
    }
}

/**
 * @param {String} selector jQuery selector string, ".someClass"
 * @param {Number} change Positive or negative number
 */
function adjustCount(selector, change) {
    var $e = $(selector);
    if (_.isNumber(change) && !_.isNaN(change) && $e.length) {
        setCount(selector, _.parseInt($e.text().replace(/[.,]/g, "")) + change);
    }
}

function setTotalResultsCount(newCount) {
    setCount("#totalResults", newCount);
}

function adjustTotalResultsCount(change) {
    adjustCount("#totalResults", change);
}

function setResultTypeCount(resultType, newCount) {
    setCount("#value_" + resultType, newCount);
}

function adjustResultTypeCount(resultType, change) {
    adjustCount("#value_" + resultType, change);
}

/**
 * Gets catalog section data from the api for the specified catalogId
 * @param {any} catalogId
 * @param {any} callback
 */
function getCatalogSections(catalogId, callback) {
    setListViewProperty('catalogSections', []);

    if (_.isNumber(catalogId) && !_.isNaN(catalogId) && catalogId > 0) {
        $.ajax({
            type: 'GET',
            url: '/api/v1/catalogs/' + catalogId + '/marketingCollections/header'
        }).done(function (data) {
            if (!_.isNil(data)) {
                setListViewProperty('catalogSections', data);
            }
        }).always(function () {
            if (_.isFunction(callback)) {
                callback();
            }
        });
    }
}

/**
 * Gets catalog showcase data from the api for the specified catalogId
 * @param {any} catalogId
 * @param {any} callback
 */
function getCatalogShowcases(catalogId, callback) {
    setListViewProperty('catalogShowcases', []);

    if (_.isNumber(catalogId) && !_.isNaN(catalogId) && catalogId > 0) {
        $.ajax({
            type: 'GET',
            url: '/api/v1/catalogs/' + catalogId + '/marketingCollections/showcase'
        }).done(function (data) {
            if (!_.isNil(data)) {
                // We're only interested in showcases that have titles
                data = _.filter(data, function (i) { return i.skuCount });
                setListViewProperty('catalogShowcases', data);
            }
        }).always(function () {
            if (_.isFunction(callback)) {
                callback();
            }
        });
    }
}

/**
 * Gets an array of catalog jump-to data available for the current, refined item list
 * @param {Object[]} catalogJumpToDatas - Array of catalog jump-to objects
 * @returns {Object[]} - Array of filtered catalog jump-to objects
 */
function getRefinedCatalogJumpToData(catalogJumpToDatas) {
    var skus = getRefinedItemIds();

    return _.filter(catalogJumpToDatas, function (catalogJumpToData) {
        return skus.indexOf(catalogJumpToData.sku) !== -1;
    });
}

/**
 * Gets an array of catalog sections available for the current, refined item list
 * @returns {Object[]} - Array of filtered catalog section objects
 */
function getRefinedCatalogSections() {
    if (!_.isNil(window.listView)) {
        return getRefinedCatalogJumpToData(getListViewProperty("catalogSections"));
    }
}

/**
 * Gets an array of catalog showcases available for the current, refined item list
 * @returns {Object[]} - Array of filtered catalog showcase objects
 */
function getRefinedCatalogShowcases() {
    if (!_.isNil(window.listView)) {
        return getRefinedCatalogJumpToData(getListViewProperty("catalogShowcases"));
    }
}

/**
 * Builds a complete list of catalog section menu items and appends them to $catalogSectionsMenuItemList
 * @param {JQuery} $catalogSectionsMenuItemList - jQuery object of the list element to append menu items to
 */
function buildCatalogSectionsMenuItemList($catalogSectionsMenuItemList) {
    if (_.isNil(window.listView) || _.isNil(window.items)) return;

    var sections = getListViewProperty("catalogSections");
    var currentSkuSequenceSortedSections = _.sortBy(sections, function (s) { return window.items.indexOf(s.sku) });

    _.forEach(currentSkuSequenceSortedSections, function (catalogSection) {
        var $catalogSectionMenuItem = $('<li></li>', {
            text: catalogSection.name,
            'data-sku': catalogSection.sku
        });

        $catalogSectionMenuItem.appendTo($catalogSectionsMenuItemList);
    });
}

/**
 * Builds a complete list of catalog showcase menu items and appends them to $catalogShowcaseMenuItemList
 * @param {JQuery} $catalogShowcasesMenuItemList - jQuery object of the list element to append menu items to
 */
function buildCatalogShowcasesMenuItemList($catalogShowcasesMenuItemList) {
    if (_.isNil(window.listView) || _.isNil(window.items)) return;

    var showcases = getListViewProperty('catalogShowcases');
    var currentSkuSequenceSortedShowcases = _.sortBy(showcases, function (s) { return window.items.indexOf(s.sku) });

    _.forEach(currentSkuSequenceSortedShowcases, function (catalogShowcase) {
        if (!catalogShowcase.skuCount) return;

        var $catalogShowcasesMenuItem = $('<li></li>', {
            text: catalogShowcase.name + ' (' + catalogShowcase.skuCount + ')',
            'data-sku': catalogShowcase.sku,
            data: {
                showcaseid: catalogShowcase.collectionId
            }
        });

        $catalogShowcasesMenuItem.appendTo($catalogShowcasesMenuItemList);
    });
}

/**
 * Builds catalog jump-to menu items from catalog jump-to data; filters visible menu items based on the current item list
 * @param {string} menuItemListSelector - jQuery selector of the list element containing menu items
 * @param {function} menuItemListBuilderFunction - Function that builds and appends items to the menu item list
 * @param {Object[]} catalogJumpToDatas - Array of catalog jump-to objects
 */
function refreshCatalogJumpToMenuItems(menuItemListSelector, menuItemListBuilderFunction, catalogJumpToDatas) {
    var $menuItemList = $(menuItemListSelector);

    if (!$menuItemList.hasClass('initialized')) {
        menuItemListBuilderFunction($menuItemList);
        $menuItemList.addClass('initialized');
    }

    $menuItemList.children().addClass('hidden').removeClass('last');

    _.forEach(catalogJumpToDatas, function (catalogJumpToData) {
        $('li[data-sku=' + catalogJumpToData.sku + ']', $menuItemList).removeClass('hidden');
    });

    $('li:not(.hidden):last', $menuItemList).addClass('last');
}

function doShowCatalogSectionsMenu(catalogSections) {
    return !_.isEmpty(catalogSections) && getListViewProperty("listType") == 1 && !areCatalogSubheadersHidden();
}

/**
 * Rebuilds the catalog section menu
 */
function rebuildCatalogSectionsMenu() {
    var catalogSections = getRefinedCatalogSections();

    if (doShowCatalogSectionsMenu(catalogSections)) {
        $('#listCatalogSectionsMenu > ul').html('').removeClass('initialized');
        refreshCatalogSectionsMenu();
    }
}

/**
 * Refreshes the catalog sections menu based on the current item list; toggles the top menu "Sections" option depending on the availability of catalog sections
 */
function refreshCatalogSectionsMenu() {
    var catalogSections = getRefinedCatalogSections(),
        doDisplayMenu = doShowCatalogSectionsMenu(catalogSections);

    if (doDisplayMenu) {
        refreshCatalogJumpToMenuItems(
            '#listCatalogSectionsMenu > ul',
            buildCatalogSectionsMenuItemList,
            catalogSections
        );
    }

    $('.listCatalogSections').toggle(doDisplayMenu);
}

function doShowCatalogShowcasesMenu(catalogShowcases) {
    return !_.isEmpty(catalogShowcases) && getListViewProperty("listType") == 1;
}

/**
 * Rebuilds the catalog showcase menu
 */
function rebuildCatalogShowcasesMenu() {
    var catalogShowcases = getRefinedCatalogShowcases();

    if (doShowCatalogShowcasesMenu(catalogShowcases)) {
        $('#listCatalogShowcasesMenu > ul').html('').removeClass('initialized');
        refreshCatalogShowcasesMenu();
    }
}

/**
 * Refreshes the catalog showcases menu based on the current item list; toggles the top menu "Showcases" option depending on the availability of catalog showcases
 */
function refreshCatalogShowcasesMenu() {
    var catalogShowcases = getRefinedCatalogShowcases(),
        doDisplayMenu = doShowCatalogShowcasesMenu(catalogShowcases);

    if (doDisplayMenu) {
        refreshCatalogJumpToMenuItems(
            '#listCatalogShowcasesMenu > ul',
            buildCatalogShowcasesMenuItemList,
            catalogShowcases
        );
    }

    $('.listCatalogShowcases').toggle(doDisplayMenu);
}

function showHideIll(sku) {
    if ($("#ilG" + sku).css("display") == "none") {
        //Show It
        $("#ilG" + sku).slideDown();
    } else {
        //Hide It
        $("#ilG" + sku).slideUp();
    }
    $("#ilAr" + sku).toggleClass("icon-drop-down-icon icon-drop-up-icon-01");
    $("#illSet" + sku).toggleClass("illSet illSetHidden");
}

function renderHiddenItemCounts(hiddenItems) {
    if (!_.isNil(hiddenItems) && hiddenItems.length > 0 && window.items) {
        for (var i = 0; i < hiddenItems.length; i++) {
            removeListViewItem(hiddenItems[i]);
        }
        $("#totalResults").html(window.items.length);
        $("#hiddenItems").html(templateCache.hiddenCount({ hiddenCount: hiddenItems.length })).show();
    }
}

// 1) Iterates through the visible titles and populates the order input with the associated line item qty
// 2) Updates the order totals summary
function getOrderQtyVisible() {
    $(".tr_Frame").each(function () {
        var sku = $(this).attr('data-item');
        o.view.updateSku(sku);
    });
    o.getOrderTotals();
}

function buildOrderSummary(orderSummary) {
    return $.extend({
        orderedTitles: 0,
        orderedUnits: 0,
        orderedTotalValue: 0
    }, orderSummary);
}

function refreshOrderSummary(orderSummary) {
    $('#orderedTitles').text(orderSummary.orderedTitles);
    $('#orderQty').text(orderSummary.orderedUnits);
    $('#orderCurrencyValue').val(orderSummary.orderedTotalValue);
    $('#orderCurrency').text(orderSummary.formattedOrderedTotalValue);
}

function updateOrderSummary(orderSummary) {
    orderSummary = buildOrderSummary(orderSummary);
    if (!orderSummary.formattedOrderedTotalValue) {
        formatCurrency(orderSummary.orderedTotalValue, ePlus.user.culture, { currency: ePlus.user.currencyCode }, function (formattedOrderTotal) {
            orderSummary.formattedOrderedTotalValue = formattedOrderTotal;
            refreshOrderSummary(orderSummary);
        });
    } else {
        refreshOrderSummary(orderSummary);
    }
}

function manageListViewHeader() {
    if ($("#listHeader").length > 0) {
        if (($("#listHeader").css("position") === "relative" || $("#listHeader").css("position") === "static") &&
            ($("#listHeader").offset().top - $(window).scrollTop() < 1)) {
            setListViewHeaderToFixed();
        }
        if ($("#listHeader").css("position") === "fixed" &&
            (window.originalHeaderPosition - $(window).scrollTop() > 0)) {
            setListViewHeaderToRelative();
        }
    }
}

function openShareMultipleMarkupsWithSelected() {
    var appUserIds = window.getSelectedItems();

    openMarkupShareTool(function () {
        for (var i in appUserIds) {
            var appUserId = appUserIds[i];
            var name = $('#contact-name-' + appUserId).html().trim();

            ePlus.modules.MarkupShareTool.addSelectedContact(name);
        }
    });
}

function setMarkupVar(sku) {
    if (typeof window.mStatus != "undefined" && sku in window.mStatus) {
        delete window.mStatus[sku];
    }
    if (typeof window.mStatus == "undefined") {
        window.mStatus = {};
    }
    if ($(".mContent_" + sku).css("display") == "none") {
        window.mStatus[sku] = 0;
    } else {
        window.mStatus[sku] = 1;
    }
    toggleMarkup(sku);
}

function toggleMarkup(sku) {
    $(".mContent_" + sku).toggle();
    $(".mArrow_" + sku).toggleClass("icon-drop-up-icon-01 icon-drop-down-icon");
}

function adjustMarkupVisibility(sku) {
    if (typeof window.mStatus != "undefined" && sku in window.mStatus) {
        if (window.mStatus[sku] != getListViewProperty("showAllMarkups")) {
            toggleMarkup(sku);
        }
    }
}

function showAllMarkups() {
    window.mStatus = {};
    window.enableSingleCheckbox($("#showAllMarkupsCheckbox"));
    var prefVal = 0;

    if ($('#showAllMarkupsCheckbox').hasClass("box_checked")) {
        $(".mContent").show();
        $(".mArrow").addClass("icon-drop-up-icon-01").removeClass("icon-drop-down-icon");
        setListViewProperty("showAllMarkups", 0);
    } else {
        $(".mContent").hide();
        $(".mArrow").removeClass("icon-drop-up-icon-01").addClass("icon-drop-down-icon");
        prefVal = 1;
        setListViewProperty("showAllMarkups", 1);
    }
    $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", { type: window.ePlus.user.userPrefType.display, name: window.ePlus.user.userPrefName.showAllMarkups, value: prefVal });
}

function showPersonalNotes() {
    var $checkbox = $('#showPersonalNotesCheckbox');

    window.enableSingleCheckbox($checkbox);

    $('.personalNotes').toggle();

    var prefVal = $checkbox.hasClass('box_checked') ? 1 : 0;
    var prefType = window.ePlus.user.userPrefType.display;
    var prefName = window.ePlus.user.userPrefName.showPersonalNotes;

    savePreference(prefType, prefName, prefVal, function () {
        ePlus.user.setPreference(prefType, prefName, prefVal);
    });
}

function showSharedMarkupNotes() {
    var $checkbox = $('#showSharedMarkupNotesCheckbox');

    window.enableSingleCheckbox($checkbox);

    var prefVal = $checkbox.hasClass('box_checked') ? 1 : 0;

    $('.shared-notes-toggle', '#itemContainer').each(function () {
        var $this = $(this);
        var sku = $this.attr('data-sku');

        if (prefVal === 1) {
            $(".iconDropDown", $this)
                .addClass('icon-drop-up-icon-01')
                .removeClass('icon-drop-down-icon');
            $('#shared-markup-notes-' + sku).removeClass('hidden');
        } else {
            $(".iconDropDown", $this)
                .removeClass('icon-drop-up-icon-01')
                .addClass('icon-drop-down-icon');
            $('#shared-markup-notes-' + sku).addClass('hidden');
        }
    });

    var prefType = window.ePlus.user.userPrefType.display;
    var prefName = window.ePlus.user.userPrefName.showSharedMarkupNotes;

    savePreference(prefType, prefName, prefVal, function () {
        ePlus.user.setPreference(prefType, prefName, prefVal);
    });
}

function showFullMarkupNotes() {
    window.enableSingleCheckbox($('#showFullMarkupsNotesCheckbox'));
    var prefVal = 0;

    if ($('#showFullMarkupsNotesCheckbox').hasClass("box_checked")) {
        prefVal = 1;
    }

    $(".tr_Frame").each(function (i) {
        var sku = $(this).attr('data-item');
        if (prefVal === 1) {
            window.ShowFullMarkupNote(sku);
        } else {
            window.ShowPartialMarkupNote(sku);
        }
    });
    $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", { type: window.ePlus.user.userPrefType.display, name: window.ePlus.user.userPrefName.showFullMarkupNotes, value: prefVal }, function () {
        ePlus.user.setPreference(window.ePlus.user.userPrefType.display, window.ePlus.user.userPrefName.showFullMarkupNotes, prefVal);
    });
}

function toggleAllRefs() {
    $(".iLookup").click();
}

function showAllSuggestions() {
    window.enableSingleCheckbox($("#showAllSuggestionsCheckbox"));
    var prefVal = 0;

    $(".suggestionSuperScript").toggle();
    if (!$('#showAllSuggestionsCheckbox').hasClass("box_checked")) {
        prefVal = 1;
    }
    $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", { type: window.ePlus.user.userPrefType.display, name: window.ePlus.user.userPrefName.showAllSuggestions, value: prefVal });
}

function showMarkupArea() {
    window.enableSingleCheckbox($("#showMarkupAreaCheckbox"));
    var prefVal = 0;

    $(".markupTitleFrame").toggle();
    if (!$('#showMarkupAreaCheckbox').hasClass("box_checked")) {
        prefVal = 1;
    }
    $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", { type: window.ePlus.user.userPrefType.display, name: window.ePlus.user.userPrefName.showMarkupArea, value: prefVal });
}

function showOrderArea() {
    window.enableSingleCheckbox($("#showOrderAreaCheckbox"));
    var prefVal = 0;

    $(".tOrdering").toggle();
    if (!$('#showOrderAreaCheckbox').hasClass("box_checked")) {
        prefVal = 1;
    }
    $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", { type: window.ePlus.user.userPrefType.display, name: window.ePlus.user.userPrefName.showOrderArea, value: prefVal });
}

function showForecast() {
    window.enableSingleCheckbox($("#showForecastCheckbox"));
    var prefVal = 0;

    $(".forecastArea").toggle();
    if (!$('#showForecastCheckbox').hasClass("box_checked")) {
        prefVal = 1;
    }
    $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", { type: window.ePlus.user.userPrefType.display, name: window.ePlus.user.userPrefName.showForecast, value: prefVal });
}

function getModifiedMarkupPriorities(skus, priority) {
    var markupId = getListViewProperty('selectedMailingID');

    if (!markupId) return;

    return skus
        .filter(function (sku) {
            var currentPriority = $('#currentPriority_' + sku).val();
            return currentPriority != priority;
        })
        .map(function (sku) {
            return {
                markupId: markupId,
                sku: sku,
                priority: priority
            };
        });
}

function selectPriorityForSkus(priority, skus, callback) {
    var priorities = getModifiedMarkupPriorities(skus, priority);
    saveMarkupPriorities(priorities, callback);
}

function selectPriority(priority, sku, row, filterType) {
    var priorities = getModifiedMarkupPriorities([sku], priority);

    saveMarkupPriorities(priorities, function () {
        var priorityOption = $('#mPOpt_' + priority).html()

        $('#markupPriority' + sku).html(priorityOption);
        window.sortrefine[window.items.indexOf(sku)].Priority_Markup = row;
        populateMarkupPriorityRefine(filterType);
    });

    $('.mPriorityOption').webuiPopover('hide');
}

function saveMarkupPriorities(priorities, callback) {
    if (priorities && priorities.length > 0) {
        ePlus.modules.markups.saveMarkupPriorities(priorities)
            .done(function () {
                refreshMarkupPriorities(priorities);

                if (typeof callback == 'function') {
                    callback();
                }
            })
            .fail(function () {
                alert(getRes('error_unexpected'));
            });
    } else if (typeof callback == 'function') {
        callback();
    }
}

function refreshMarkupPriorities(priorities) {
    priorities.forEach(function (priority) {
        var sku = priority.sku;
        var currentPriority = $('#currentPriority_' + sku).val();
        var listViewPriority = $('#priority-' + sku).attr('data-priority');

        if (listViewPriority !== currentPriority) {
            currentPriority = listViewPriority;
        }

        $('#markupPriority' + sku).css('background-color', '#fff');
        reRenderPriorityStyling(sku, priority.priority, currentPriority);
    });
}

function reRenderPriorityStyling(sku, priority, oldPriority) {
    if (priority != oldPriority) {
        var $titleFrame = $("#as_" + sku);
        var rowIndex = $("#check_" + sku).data("row-index");
        var originalOrderIndex = $("#check_" + sku).data("original-order");
        $.url = "/GetTreelineControl.aspx?controlName=/uc/listviews/ListView_Title_Row_Priority.ascx&sku=" + sku + "&displayPriority=" + priority;
        if (_.isNumber(rowIndex)) {
            $.url += "&rowIndex=" + rowIndex;
        }
        if (_.isNumber(originalOrderIndex)) {
            $.url += "&originalOrderIndex=" + originalOrderIndex;
        }
        if (!oldPriority) {
            $titleFrame.removeClass(function () {
                return (this.className.match(/tr_Br_[0-9]*/ig) || []).join(' ');
            });
        }

        $("#pr_" + sku).load($.url, function () {
            $("#currentPriority_" + sku).val(priority);
            $titleFrame.removeClass("tr_Br");
            if (priority == 0 || priority == 4) {
                $titleFrame.addClass("tr_Br");
            } else {
                $titleFrame.addClass("tr_Br_" + priority);
            }
            $titleFrame.removeClass("tr_Br_" + oldPriority);
            if (priority == 10) {
                $titleFrame.addClass("highlightedTitle");
            } else {
                $titleFrame.removeClass("highlightedTitle");
            }
            if (priority == 12 || oldPriority == 12) {
                $("#lL_" + sku).toggleClass("legacyLeft legacyLeftFeatured");
                $("#lM_" + sku).toggleClass("legacyMiddle legacyMiddleFeatured");
                var jacketWidth = 100
                if (priority == 12) {
                    jacketWidth = 150
                }
                $.url = "/GetTreelineControl.aspx?controlName=/uc/product/modules/maincover.ascx&sku=" + sku + "&width=" + jacketWidth;
                $("#lL_" + sku).load($.url);
            }
        });
    }
}
function disableListViewHeaderScrollHandler() {
    $(window).off('scroll.listViewHeader');
}

function enableListViewHeaderScrollHandler() {
    disableListViewHeaderScrollHandler();

    $(window).on('scroll.listViewHeader', function () {
        // If a jump-to is in progress, don't allow adjustments to the list view header until we arrive at that location
        if (getListViewProperty("jumpToPosition") && $(window).scrollTop() == getListViewProperty("jumpToPosition")) {
            setListViewProperty("jumpToPosition", null);
            return;
        }

        manageListViewHeader();
    });
}

function setListViewHeaderToRelative() {
    $('#listHeader').css({
        position: 'relative',
        top: '',
        left: '',
        right: '',
        width: '',
        'z-index': ''
    });
}

function setListViewHeaderToFixed() {
    var listViewHeaderOffset = $('.app-header', $('#pageHeader')).height();
    $('#listHeader').css({
        position: 'fixed',
        top: (listViewHeaderOffset + 1) + 'px',
        width: $('#listContent').width(),
        margin: '0 auto',
        'z-index': 900003
    });
}

function goToCatalogFromOrderPage(orderId, catalogId) {
    $(".ltRow, .rContent").addClass("progressBackground");
    setListViewProperty("selectedOrderID", orderId);

    $.getJSON("/getJSONData.aspx?builder=SetActiveOrder&orderID=" + orderId, function () {
        pageChange('catalogID=' + catalogId);
    });
}

function isSortRefineInitialized() {
    return ePlus.modules.listView.refinements.isSortRefineInitialized();
}

function getRefineNumbers() {
    setRefinementInProgress(true);
    ePlus.modules.listView.refinements.counts.execute();
    setRefinementInProgress(false);
}

function changeRefineMessage(status) {
    if (status === "notSelected") {
        $(".noSavedSearchHelp").show();
        $(".savedSearchHelp").hide();
    } else {
        $(".noSavedSearchHelp").hide();
        $(".savedSearchHelp").show();
    }
}

function closeListViewAlerts() {
    $(".listViewAlert").parent().hide();
}

function hideListViewProgressTracker() {
    $(".homeProcessDiv").remove();
}

/**
 * Removes list view items from #tempList + sequence
 * @param {Number} sequence
 * @param {function} callback
 */
function cleanupListView(sequence, callback) {
    $("#tempList" + sequence).empty();

    if (typeof callback === "function") {
        callback();
    }
}

function clearListViewLoadingState() {
    window.stopLoading = null;

    if (typeof pageTracking !== 'undefined') {
        pageTracking.stopTrackPage();
    }
}

function initializeContactsListView() {
    for (var i = 0; window.items && i < window.items.length; i++) {
        var contactId = window.items[i];
        var $contactCountContainer = $("#group-contact-count-" + contactId);
        var userId = $("#as_" + contactId).data("id");
        var container = "#pageContent";

        var $groupsContent = $('#groups-' + contactId);
        var $downloadCountContainer = $("#contact-downloads-count-" + contactId);
        var $requestsCountContent = $("#drc-request-contact-count-" + contactId);
        var $reviewCountContainer = $("#contact-reviews-count-" + contactId);
        var $orderCountContainer = $("#order-contact-count-" + contactId);

        if (hasContactContent(contactId, $downloadCountContainer)) {
            initDashItemsPopovers($downloadCountContainer, container, userId, getEnumValue("resultType", "TITLEDOWNLOAD_CONTACT"));
        }

        if (hasContactContent(contactId, $reviewCountContainer)) {
            initDashItemsPopovers($reviewCountContainer, container, userId, getEnumValue("resultType", "TITLEREVIEW_CONTACT"));
        }

        if (hasContactContent(contactId, $groupsContent)) {
            $contactCountContainer.webuiPopover({
                content: $groupsContent.html(),
                trigger: 'hover',
                placement: 'bottom',
                container: container
            });
        }

        if (hasContactContent(contactId, $orderCountContainer)) {
            initDashItemsPopovers($orderCountContainer, container, userId, getEnumValue("resultType", "ORDERS_SHARED_WITH_ME"));
        }

        if (hasContactContent(contactId, $requestsCountContent)) {
            initDashItemsPopovers($requestsCountContent, container, userId, getEnumValue("resultType", "TITLEREQUEST_CONTACT"));
        }
    }
}

function initializeContactOrganizationsListView() {
    var container = "#pageContent";

    for (var i = 0; window.items && i < window.items.length; i++) {
        var orgId = window.items[i];
        var $requestCountContainer = $("#org-request-count-" + orgId);
        var $downloadCountContainer = $("#org-download-count-" + orgId);
        var $reviewCountContainer = $("#org-review-count-" + orgId);
        var $orderCountContainer = $("#org-order-count-" + orgId);
        if (hasContactContent(orgId, $requestCountContainer)) {
            $requestCountContainer.webuiPopover({
                type: 'async',
                trigger: "hover",
                cache: false,
                url: "/GetTreelineControl.aspx?controlName=/uc/listviews/controls/ContactOrganizationAttributeView.ascx&attributeType=requests&orgId=" + orgId,
                placement: 'bottom',
                container: container
            });
        }
        if (hasContactContent(orgId, $downloadCountContainer)) {
            $downloadCountContainer.webuiPopover({
                type: 'async',
                trigger: "hover",
                cache: false,
                url: "/GetTreelineControl.aspx?controlName=/uc/listviews/controls/ContactOrganizationAttributeView.ascx&attributeType=downloads&orgId=" + orgId,
                placement: 'bottom',
                container: container
            });
        }
        if (hasContactContent(orgId, $reviewCountContainer)) {
            $reviewCountContainer.webuiPopover({
                type: 'async',
                trigger: "hover",
                cache: false,
                url: "/GetTreelineControl.aspx?controlName=/uc/listviews/controls/ContactOrganizationAttributeView.ascx&attributeType=reviews&orgId=" + orgId,
                placement: 'bottom',
                container: container
            });
        }
        if (hasContactContent(orgId, $orderCountContainer)) {
            $orderCountContainer.webuiPopover({
                type: 'async',
                trigger: "hover",
                cache: false,
                url: "/GetTreelineControl.aspx?controlName=/uc/listviews/controls/ContactOrganizationAttributeView.ascx&attributeType=orders&orgId=" + orgId,
                placement: 'bottom',
                container: container
            });
        }
    }
}

function hasContactContent(id, $containerSelector) {
    return id && $containerSelector.length > 0 && $containerSelector.text().trim() !== "";
}

function initializeListView() {
    validateAndToggleFunctionality();

    window.ePlus.modules.listView.initializeByItemType();

    renderPaging(getPageCount());
    togglePagingSection();

    if (window.listViewFinishedLoadingCallback && typeof (window.listViewFinishedLoadingCallback) === 'function') {
        window.listViewFinishedLoadingCallback();
        delete window.listViewFinishedLoadingCallback;
    }
}

function validateAndToggleFunctionality() {
    if (!window.autoCompleteJson) {
        $("#autoComplete").hide();
    }

    if (!isSortRefineInitialized()) {
        $(".refineArea").hide();
        $(".listSort").hide();
    }
}

function displayRelevantRows(startPos, currentItem, callback) {
    window.scrollTo(0, 0);
    displayRelevantRowsWithoutScrolling(startPos, currentItem, callback);
}

function displayRelevantRowsWithoutScrolling(startPos, currentItem, callback) {
    var resultType = getListViewProperty("resultType");
    var asyncFunctions = [];

    switch (getListViewProperty("itemType")) {
        case getEnumValue('itemType', 'TITLE'):
            var LOAD_BUFFER = 20;
            var listType = +getListViewProperty("listType");

            if (listType == getEnumValue("listType", "GRIDVIEW")) {
                asyncFunctions = [
                    async.apply(renderRelevantTitleRows, startPos, 50, 1, currentItem, resultType),
                    async.apply(cleanupListView, 2)
                ];
            } else {
                asyncFunctions = [
                    async.apply(renderRelevantTitleRows, startPos, 20, 1, currentItem, resultType),
                    async.apply(renderRelevantTitleRows, startPos + LOAD_BUFFER, 30, 2, currentItem, resultType)
                ];
            }
            break;
        default:
            asyncFunctions = [
                async.apply(renderRelevantItemRows, startPos, 50, 1, resultType)
            ];
            break;
    }

    if (asyncFunctions.length) {
        async.parallel(asyncFunctions, function () {
            initializeListView();
            clearListViewLoadingState();
            jumpToEnd(currentItem, callback);
        });
    }
}

// User Tag Functions
function showTitleUserTagCount(sku, count) {
    if (_.isNil(sku) || !_.isNumber(count)) {
        return;
    }

    var $myTagsSku = $('.myTags_' + sku),
        $simpleTagsSku = $('.simpleTags_' + sku);

    if ($myTagsSku.length) {
        $myTagsSku.html(count).show();
        $('.myT_' + sku).show();

        if ($simpleTagsSku.length) {
            $simpleTagsSku.show();
        }
    }
}

function hideTitleUserTagCount(sku, count) {
    if (_.isNil(sku) || !_.isNumber(count)) {
        return;
    }

    var $myTagsSku = $('.myTags_' + sku),
        $simpleTagsSku = $('.simpleTags_' + sku);

    if ($myTagsSku.length) {
        $myTagsSku.html(count).hide();

        if (!$('#orgTags_' + sku).length) {
            $('.myT_' + sku).hide();
        }

        if ($simpleTagsSku.length) {
            $simpleTagsSku.hide();
        }
    }
}

function refreshTitleUserTagCount(sku, count) {
    if (_.isNil(sku) || !_.isNumber(count)) {
        return;
    }

    var $myTagsSku = $('.myTags_' + sku);

    count ?
        showTitleUserTagCount(sku, count) :
        hideTitleUserTagCount(sku, count);
}

function refreshTitleUserTagCounts(skuTagCounts) {
    _.forEach(skuTagCounts, function (skuTagCount) {
        var sku = skuTagCount.text,
            count = _.parseInt(skuTagCount.data);

        refreshTitleUserTagCount(sku, count);
    });
}

function refreshUserTagRefinements() {
    var filterType = getEnumValue('filterType', 'ALLACCOUNTTAG');
    populateOrgTagRefine(filterType, 'Tags_Account', 'Untagged Titles', true);
}

function refreshUserTagTotals(skus) {
    if (_.isEmpty(skus)) {
        closeModal();
        return;
    }

    $.post('/getJSONData.aspx?builder=RefreshUserTagTotals', { skus: skus }, function (data) {
        refreshTitleUserTagCounts(data);
        refreshUserTagRefinements();
        closeModal();
    }, 'json');
}

// Markup Tag Functions
function refreshMarkupTagRefinements() {
    var type = getEnumValue('filterType', 'MARKUPTAG');
    ePlus.modules.listView.refinements.counts.reset([type]);
    populateMarkupTagRefine(type, 'Untagged Titles');
}

function refreshMarkupTagTotals() {
    refreshMarkupTagRefinements();
    closeModal();
}

function getAccountNotesTags(skuList) {
    if (skuList && skuList.length > 0) {
        window.openListViewAccountNotes(skuList);
        window.openListViewAccountTags(skuList);
    }
}

function initListViewEventHandlers() {
    var eventHandlers = getListViewProperty('eventHandlers');
    if (_.isArray(eventHandlers)) {
        if ('ontouchstart' in window) {
            eventHandlers.push(getListViewSwipeEvents());
        }
    }
}
function getListViewSwipeEvents() {
    return new Swipeable({
        'touchTargetMaxStartX': 260,
        'swipeLeftAction': ePlus.modules.leftNav.closeLeftNav,
        'swipeRightAction': ePlus.modules.leftNav.openLeftNav,
        'touchTarget': document.getElementById('listHome')
    });
}
function removeListViewEventHandlers() {
    var eventHandlers = getListViewProperty('eventHandlers');
    if (_.isArray(eventHandlers)) {
        for (var i = eventHandlers.length - 1; i >= 0; i--) {
            if (eventHandlers[i] && typeof eventHandlers[i].destroy === 'function') {
                var eventHandler = eventHandlers[i].destroy();
                eventHandler = null;
                eventHandlers.splice(i, 1);
            }
        }
    }
}

function toggleLayer(divID) {
    $.elem = $('#' + divID + '-content');
    $.elem.toggle();
    if ($.elem.is(":visible")) {
        $('#' + divID + '-arrow').addClass("icon-drop-up-icon-01");
        $('#' + divID + '-arrow').removeClass("icon-drop-down-icon");
    }
    else {
        $('#' + divID + '-arrow').addClass("icon-drop-down-icon");
        $('#' + divID + '-arrow').removeClass("icon-drop-up-icon-01");
    }
}

function checkAllRefinements(selectAllCheckbox) {
    var sectionClass = $(selectAllCheckbox).data("section-class");
    var checkChildren = !$(selectAllCheckbox).hasClass("box_checked");
    var section = $(selectAllCheckbox).data("filter-type");

    $(selectAllCheckbox).toggleClass("box_unchecked box_checked");

    toggleAllChildrenRefinements(section, sectionClass, checkChildren);
    showMoreRefinements(section, checkChildren);
    toggleAllShowMoreChildrenRefinements(section, sectionClass, checkChildren);

    buildFilterOptions(section);
}

function toggleAllChildrenRefinements(section, sectionClass, checkChildren) {
    $("." + section + "_cont").first().children('.' + sectionClass + ":visible").each(function () {
        if (checkChildren) {
            $(this).children().first().removeClass("box_unchecked").addClass("box_checked");
        }
        else {
            $(this).children().first().removeClass("box_checked").addClass("box_unchecked");
        }
    });
}

function toggleAllShowMoreChildrenRefinements(section, sectionClass, checkChildren) {
    if ($(".showMore_" + section).length) {
        $(".showMore_" + section).children('.' + sectionClass + ":visible").each(function () {
            if (checkChildren) {
                $(this).children().first().removeClass("box_unchecked").addClass("box_checked");
            }
            else {
                $(this).children().first().removeClass("box_checked").addClass("box_unchecked");
            }
        });
    }
}

function showPublicityEvents(id, sku) {
    var $publicityCampaignContainer = $('#' + id);
    $publicityCampaignContainer.toggle();
    if (!$publicityCampaignContainer.is(":hidden")) {
        $('#' + id + '-arrow').removeClass("icon-drop-down-icon").addClass("icon-drop-up-icon-01");
        $.url = "/GetTreelineControl.aspx?controlName=/uc/PublicityCampaign/HomePage_Publicity.ascx&action=refresh&comesfrom=listview&catalogID=0&group=publicity&sku=" + sku;
        $publicityCampaignContainer.load($.url);
    }
    else {
        $('#' + id + '-arrow').addClass("icon-drop-down-icon").removeClass("icon-drop-up-icon-01");
    }
}

function reloadRefine() {
    $.url = "/GetTreelineControl.aspx?controlName=/uc/listviews/ListHomeRefine.ascx&resultType=" + getListViewProperty("resultType") + "&dashType=" + getListViewProperty("dashboardType") + "&itemType=" + getListViewProperty("itemType");
    $("#leftNavRefine").load($.url);
}

function undoDRCProcessing(requestID, status, sku, appID) {
    $.getJSON("/GetJSONData.aspx?m=Galley&builder=UndoProcessedDRC", { requestID: requestID, status: status, sku: sku, appID: appID }, function (data) {
        if (data.code == "OK") {
            window.GetResultOption(18, '');
            window.GetResultOption(181, '');
            $(".undo_" + requestID).hide();
            $(".proc_" + requestID).hide();
            $(".undone_" + requestID).show();
        }
        else
            alert(data.text);
    });
}

function addContactFromList(appUserId, orgId, emailAdd) {
    $.getJSON("/getJSONData.aspx?builder=AddContactByEmailAddress",
        {
            orgID: orgId,
            emailAddresses: emailAdd,
            orgOnly: 'false',
            viewType: "user"
        },
        function (data) {
            $("#contactNo_" + appUserId).addClass('hidden');
            $("#contactYes_" + appUserId).removeClass('hidden');
        });
}

function removeContactFromList(appUserId, emailAdd, primaryId) {
    $.post("/getJSONData.aspx?builder=DeleteContacts_EmailAddress",
        {
            emailList: emailAdd
        },
        function (data) {
            $("#contactNo_" + appUserId).removeClass('hidden');
            $("#contactYes_" + appUserId).addClass('hidden');

            if (!(typeof primaryId === "undefined" || _.isNull(primaryId))) {
                $("#addContactIcon_" + primaryId).show();
                $("#contactYes_" + primaryId).hide();
                $("#contactIcon_" + primaryId).hide();
            }
        }, "json");
    return false;
}

function hideItemInDashboard(itemId, laneId, resultType, dashboardType, widgetId) {
    $('#dashLaneMain_' + laneId).addClass('progressBackground');
    $('#item_' + itemId + '_' + laneId).hide();

    var settings = {
        laneId: laneId,
        resultType: resultType,
        widgetId: widgetId,
        refresh: true
    }

    hideItem(itemId, dashboardType, resultType, false, window.ePlus.modules.dashboard.initialDashLoad.bind(null, settings));
}

function exportContactsIndividuals() {
    downloadFile('/api/me/contacts/individuals/export');
}

function exportSelectedContactsIndividuals() {
    var contactIds = window.getSelectedItems();

    downloadFile('/api/me/contacts/individuals/export', contactIds);
}

function setShowAsAccountStatus(context, orgId) {
    if (context && orgId) {
        var values = {
            targetOrgId: orgId,
            isAccount: $(context).find("input[type='checkbox']").prop("checked")
        };

        $.getJSON("/GetJSONData.aspx?builder=ToggleAddressBookOrganizationAccountStatus", values, function (data) {
            if (data.code !== "OK") {
                modalAlert(data.text);
            } else {
                $("#is-account-" + orgId).prop('checked', values.isAccount);
            }
        });
    }
}

function undoPendingPrc(type, userRequestOrderLineItemId, sku) {
    window.ePlus.modules.prc.deletePrcRequest(type, userRequestOrderLineItemId, function () { togglePendingPrcToRequestPrc(sku); });
}

function togglePendingPrcToRequestPrc(sku) {
    $('#requestInfoMessage').html("");
    var url = "/GetTreelineControl.aspx?controlName=/uc/listviews/controls/PrcActions.ascx&sku=" + sku + "&isRequest=true";
    $("#prcContainer_" + sku).load(url);
}

function initPrcActions() {
    if (window.isTouch) {
        $(".prcUndo", $("#itemContainer")).removeClass("hidden").addClass("clickable");
    }
    else {
        $(".prcArea", $("#itemContainer")).hover(
            function () {
                $(this).find("div.prcUndo").removeClass("hidden").addClass("clickable");
            },
            function () {
                $(this).find("div.prcUndo").removeClass("clickable").addClass("hidden");
            });
    }
}



function getMarkupNoteContainerElements(sku) {
    return sku ? $('#markupNoteContainer_' + sku) : $('.myMarkupNote', "#itemContainer");
}

function enableMarkupNoteEditor(sku) {
    var $elem = getMarkupNoteContainerElements(sku);

    $elem.off('click.markupNoteEditor').on('click.markupNoteEditor', function (e) {
        if (doHandleMarkupEditEvent(e)) {
            // If a link in the markup note is clicked, follow the link and don't open the markup editor
            if (e.target && (e.target.tagName == "A" || (e.target.tagName == "IMG" && e.target.parentElement.tagName == "A"))) {
                return true;
            }

            var sku = $(this).closest('.tr_Frame').attr('data-item');

            editMarkupNote(sku, getListViewProperty('itemID'), getListViewProperty('selectedMailingID'));
        }
    });
}

function doHandleMarkupEditEvent(e) {
    return doesEventExist(e) && shouldEventBeProcessed(e);
}

function doesEventExist(e) {
    return e && e.originalEvent && e.originalEvent;
}

function shouldEventBeProcessed(e) {
    return (e.originalEvent.detail && e.originalEvent.detail === 1) ||
        window.isIeEleven;
}

function disableMarkupNoteEditor(sku) {
    var $elem = getMarkupNoteContainerElements(sku);

    $elem.off('click.markupNoteEditor');
}

function editMarkupNote(sku, catalogId, mailingId) {
    var activeMarkupNoteEditorSku = getListViewProperty('activeMarkupNoteEditor');

    if (activeMarkupNoteEditorSku) {
        openNavigateToOpenMarkupEditorModal(activeMarkupNoteEditorSku);
        return;
    }

    doUseInlineEditor = ePlus.user.getPreference('markup', 'useInlineEditor') === 'true';

    var isbn13 = $('#markupTitleFrame' + sku).data('isbn13');

    setListViewProperty('activeMarkupNoteEditor', {
        sku: sku,
        isbn13: isbn13
    });

    if (doUseInlineEditor) {
        ePlus.modules.inlineMarkupNoteEditor.initialize({
            sku: sku,
            catalogId: catalogId,
            mailingId: mailingId
        });
    } else {
        openMarkupEditor(sku, mailingId, catalogId);
    }
}

function openNavigateToOpenMarkupEditorModal(activeMarkupNoteEditorSku, continueCallback) {
    var buttons = {};

    var closeModalAndRevertUrl = function () {
        closeModal();
        window.stopLoading = null;
        history.replaceState(null, '', '#catalogID=' + window.listView.itemID);
    }

    buttons[getRes('go_to_open_editor')] = function () {
        jumpToItem(activeMarkupNoteEditorSku.sku);
        closeModalAndRevertUrl();
    };

    var message = getRes('error_markup_editor_open_sku').replace('{0}', activeMarkupNoteEditorSku.isbn13);
    if (typeof continueCallback === 'function') {

        buttons[getRes('continue')] = function () {
            closeModal();
            continueCallback();
        }

        message += ' ' + getRes('selecting_continue_will_cause_any_unsaved_changes_to_be_lost');
    } else {
        message += ' ' + getRes('please_close_or_save_current_editor_before_continuing');
    }

    modalConfirm({
        message: message,
        width: "400px",
        height: "200px",
        buttons: buttons,
        zindex: 100000000,
        onClose: closeModalAndRevertUrl
    });
}

function toggleAllEventGridRequestsPreference(type, name) {
    window.enableSingleCheckbox($('#showAllEventGridRequestsCheckbox'));
    var prefVal = 1;

    if ($('#showAllEventGridRequestsCheckbox').hasClass("box_checked")) {
        prefVal = 0;
    }

    $("div.eventRequests", ".eventRequestsContainer").toggleClass("hidden", prefVal);
    $(".iconDropDown", ".eventRequestsContainer").toggleClass("icon-drop-down-icon icon-drop-up-icon-01");

    $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", { type: type, name: name, value: prefVal }, function () {
        ePlus.user.setPreference(type, name, prefVal);
    });
}

function toggleEventGridRequest(element, sku) {
    var doShowContent = !$(element).hasClass("icon-drop-up-icon-01");
    $("#eventRequestTable" + sku).toggleClass("hidden", !doShowContent);
    $(element).toggleClass("icon-drop-down-icon icon-drop-up-icon-01")
}

function enablePersonalNoteEditor(sku, doShowPreRelease) {
    var $elem = sku ? $('#curNote_' + sku) : $('.personalNotes', "#itemContainer");
    $elem.off('click.personalNote').on('click.personalNote', function (e) {
        if (e) {
            // If a link in the markup note is clicked, follow the link and don't open the markup editor
            if (e.target && (e.target.tagName == "A" || (e.target.tagName == "IMG" && e.target.parentElement.tagName == "A"))) {
                return true;
            }
        }

        var sku = $(this).closest('.tr_Frame').attr('data-item');
        editPersonalNote(sku, doShowPreRelease);
    });
}

function editPersonalNote(sku, showPreRelease) {
    var activeMarkupNoteEditor = getListViewProperty('activePersonalNoteEditor');

    if (activeMarkupNoteEditor) {
        alert(getRes('error_inline_editor_open'));
        return;
    }

    var doUseInlineEditor = ePlus.user.getPreference('noteToSelf', 'useInlineEditor') === 'true' && showPreRelease === true;
    var enableEditorFunction = function () {
        enablePersonalNoteEditor(sku, showPreRelease);
    }

    if (doUseInlineEditor) {
        enablePersonalNoteCheckBoxes(sku);
        setListViewProperty('activePersonalNoteEditor', sku);

        ePlus.modules.inlineEditor.initialize({
            sku: sku,
            editorId: 'curNoteEditor_' + sku,
            namespace: 'personalNote',
            activeEditorNamespace: 'activePersonalNoteEditor',
            editorTextContainer: 'curNoteText_' + sku,
            eventContainer: 'curNote_' + sku,
            onSave: savePersonalNote,
            onDelete: deletePersonalNote,
            onClose: function () { disablePersonalNoteCheckBoxes(sku); },
            enableEditor: enableEditorFunction
        });
    } else {
        openPersonalNote(sku);
    }
}

function enablePersonalNoteCheckBoxes(sku) {
    $('#makeDefault' + sku + ', #publisherVisible' + sku).off('click').on('click', function (e) {
        enableSingleCheckbox($(this));
        $("#" + $(this).attr("id") + "_Hidden").val($(this).hasClass("box_checked"));
    });

    $("#publisherVisibleContainer" + sku).show();
    $("#makeDefaultContainer" + sku).show();
}

function disablePersonalNoteCheckBoxes(sku) {
    $('#makeDefault' + sku + ', #publisherVisible' + sku).off('click');
    $("#publisherVisibleContainer" + sku).hide();
    $("#makeDefaultContainer" + sku).hide();
}

function deletePersonalNote(editor, closeEditorCallback) {
    editor.setData('', function () {
        savePersonalNote(editor);
        closeEditorCallback();
    });
}

function savePersonalNote(editor) {
    var note = editor.getData();
    var sku = getListViewProperty('activePersonalNoteEditor');

    var $publisherVisible = $('#publisherVisible' + sku);
    var publisherVisibleHidden = $publisherVisible.length ? $publisherVisible.hasClass('box_checked') : false;

    var $makeDefault = $('#makeDefault' + sku);
    var makeDefaultHidden = $makeDefault.length ? $makeDefault.hasClass('box_checked') : false;

    var values = {
        sku: sku,
        publisherVisible_Hidden: publisherVisibleHidden,
        makeDefault_Hidden: makeDefaultHidden
    }

    values["sequence" + sku] = 0;
    values["text" + sku] = encodeURIComponent(note);

    $.post("/GetJSONData.aspx?builder=SaveNote", values, function (data) {
        $("#curNoteText_" + sku).html(note);
        $("#curNoteEditor_" + sku).html(note);
        openListViewNote(sku);
    });
}

function highlightElementAnimation($elem, transitionClass, highlightClass, transitionDuration) {
    if (!transitionClass) transitionClass = "transition-all";
    if (!highlightClass) highlightClass = "highlight-element";
    if (!transitionDuration) transitionDuration = 3000;
    $elem.addClass(transitionClass).addClass(highlightClass).delay(transitionDuration).queue(function () {
        $(this).removeClass(highlightClass);
        $(this).dequeue();
    }).delay(transitionDuration).queue(function () {
        $(this).removeClass(transitionClass);
        $(this).dequeue();
    });
}

function toggleAllSubrights(type, name) {
    window.enableSingleCheckbox($('#showAllSubrightsCheckbox'));
    var prefVal = 0;

    if ($('#showAllSubrightsCheckbox').hasClass("box_checked")) {
        $(".tr_Frame").each(function () {
            var thisSku = $(this).attr('data-item');
            if ($("#subrights_" + thisSku + "-Wrapper").length) {
                showSubRights('subrights' + thisSku, thisSku);
            }
        });
        prefVal = 1;
        $(".subRightsDiv", $("#itemContainer")).show();
    } else {
        $(".iconDropDown.subRightsArrow", $("#itemContainer")).removeClass("icon-drop-up-icon-01").addClass("icon-drop-down-icon");
        $(".subRightsDiv", $("#itemContainer")).hide();
    }

    savePreference(type,
        name,
        prefVal,
        function () {
            setListViewProperty("showAllSubrights", prefVal);
        });
}

function toggleSubrights(id, sku) {
    $('#' + id + '-plus-arrow').show();

    if ($('#' + id).is(':hidden')) {
        showSubRights(id, sku);
    } else {
        $('#' + id).hide();
        $('#' + id + '-arrow').removeClass("icon-drop-up-icon-01").addClass("icon-drop-down-icon");
    }
}

function showSubRights(id, sku) {
    $('#' + id).show();
    $('#' + id + '-arrow').removeClass("icon-drop-down-icon").addClass("icon-drop-up-icon-01");
    getSubrightsContent(id, sku);
}

function getSubrightsContent(id, sku) {
    $.url = "/GetTreelineControl.aspx?controlName=/uc/product/ProductSubRights.ascx&source=listview&sku=" + sku;
    $('#' + id).load($.url);
}

//Tags
function UpdateQuickTagAutoCompletes(acString, acInputs) {
    window.quickTagAutoComplete = acString;

    if (acInputs && acInputs.length) {
        var dataArray = acString.split("~");

        acInputs.each(function () {
            $(this).setOptions({ data: dataArray });
        });
    }
}

function UpdateQuickCatalogTagAutoCompletes(acString) {
    window.quickCatalogTagAutoComplete = acString;
}

function UpdateQuickGroupAutoCompletes(acString) {
    $("#quickGroupAutoComplete").val(acString);
}

function getMultipleDashboardCounts(dashboardType, resultTypes) {
    if (!resultTypes || !_.isNumber(dashboardType)) {
        return;
    }
    $.ajax({
        type: 'POST',
        url: 'api/listViews/' + dashboardType + '/counts',
        contentType: 'application/json; charset=utf-8',
        data: JSON.stringify(resultTypes)
    }).done(populateDashboardCounts);
}

function populateDashboardCounts(resultTypeCountsMap) {
    if (resultTypeCountsMap) {
        for (var resultType in resultTypeCountsMap) {
            $("#value_" + resultType).html(resultTypeCountsMap[resultType]);
        }
    }
}

function togglePagingSection() {
    var page = $('.pagingSelected', $('#pagingRow')).data("page");
    $('#page-prev').toggleClass('hidden', window.pages === 0 || typeof page === 'undefined' || page == 1);
    $('#page-next').toggleClass('hidden', window.pages === 0 || typeof page === 'undefined' || page == window.pages);

    var doHidePageSection = $('#page-next').hasClass('hidden') && $('#page-prev').hasClass('hidden') ||
        getListViewProperty('listType') === getEnumValue('listType', 'ANALYSISOFTITLESET');
    $("#paging-section").toggleClass('hidden', doHidePageSection);
}

function getPageCount() {
    if (!window.sortrefine) {
        return 0;
    }

    var itemCount = window.sortrefine.filter(function (v) {
        return v.filteredOut === 0;
    }).length;

    return Math.floor((itemCount - 1) / 50) + 1;
}

function initializeImprintGroupListView() {
    window.ePlus.modules.listViewImprintGroupGridRow.initActions();
}

function addRoleAssignmentClickEvents(removeAssignmentSelector, addAssignmentSelector) {
    $(removeAssignmentSelector, $('#itemContainer')).on('click', function () {
        var userId = $(this).data('item');
        var groupId = $('#assigned-dropdown-' + userId).val();

        if (!userId || !groupId) return;

        $.ajax({
            type: 'DELETE',
            url: '/api/users/' + userId + '/groups/' + groupId
        })
            .done(function () {
                updateUserRoleUI(userId);
            })
            .fail(function () {

            });
    });

    $(addAssignmentSelector, $('#itemContainer')).on('click', function () {
        var userId = $(this).data('item');
        var groupId = $('#unassigned-dropdown-' + userId).val();

        if (!userId || !groupId) return;

        $.ajax({
            type: 'POST',
            url: '/api/users/' + userId + '/groups/' + groupId
        })
            .done(function () {
                updateUserRoleUI(userId);
            })
            .fail(function () {

            });
    });
}

function updateUserRoleUI(userId) {
    $('#user-' + userId + '-role-container').load('/GetTreelineControl.aspx?controlName=/uc/listviews/Users/ListView_UserAdmin_RoleAssignmentDropdown.ascx&ID=' + userId, function () {
        addRoleAssignmentClickEvents('#remove-role-' + userId + '-btn', '#add-role-' + userId + '-btn');
    });
}

function activateOnixTooltips() {
    window.ePlus.modules.support.onixToolTips.initialize();
    listView.onixTooltips = true;
}

function disableActivateOnixTooltipsMenuOption() {
    $('#action_activate_onix_tooltips')
        .removeClass('menuOption')
        .addClass('menuOptionDisabled');
}

function closeOtherActions() {
    $('.otherActions').webuiPopover('hide');
};
function populateAnalyticHomeChart(dashType) {
    $("#chart_" + dashType).html('Loading Chart');
    $.url = "/GetTreelineControl.aspx?controlName=/uc/analytics/dashContent/AnalyticsChart_" + dashType + ".ascx";
    $("#chart_" + dashType).load($.url);
}

function openAnalyticOptions(dashType, resultType) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/analytics/dashOptions/AnalyticsOptions_" + dashType
        + ".ascx&resultType=" + resultType;
    openModal(url, "750px", "270px");  
}

function openAnalyticOptionsByLaneKey(laneKey) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/analytics/dashOptions/AnalyticsOptions.ascx"
        + "&laneKey=" + laneKey;
    openModal(url, "825px", "270px");  
}

function getInprocessHeader(loadingString) {
    return
    "<div class='inProcessHeader'>"
        + "<div id='progressAnimation' class='column'></div>"
        + "<div class='columnSpaced'>" + loadingString + " Dashboard</div>"
        + "<div style='clear:both;'></div>"
    + "</div>"
}

function populateDashboard(dashType, subDash, loadingString) {
    $("#dashboard_" + dashType).html(getInprocessHeader(loadingString));
    $.url = "/GetTreelineControl.aspx?controlName=/uc/analytics/Analytics_Home_Dashboard.ascx"
        + "&dashType=" + dashType + "&subDash=" + subDash;
    $("#dashboard_" + dashType).load($.url);
}

function populateLane(laneKey, dashType, subDash, loadingString, isInlineLocationSelectorIncluded) {
    $("#dashboard_" + dashType).html(getInprocessHeader(loadingString));
    $.url = "/GetTreelineControl.aspx?controlName=/uc/analytics/Analytics_Home_Lane.ascx"
        + "&laneKey=" + laneKey + "&subDash=" + subDash;
    if (typeof isInlineLocationSelectorIncluded !== "undefined" && isInlineLocationSelectorIncluded !== null) {
        $.url = $.url + "&isInlineLocationSelectorIncluded=" + isInlineLocationSelectorIncluded.toString();
    }
    $("#dashboard_" + dashType).load($.url);
};
(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
    typeof define === 'function' && define.amd ? define(['exports'], factory) :
    (factory((global.async = global.async || {})));
}(this, (function (exports) { 'use strict';

/**
 * A faster alternative to `Function#apply`, this function invokes `func`
 * with the `this` binding of `thisArg` and the arguments of `args`.
 *
 * @private
 * @param {Function} func The function to invoke.
 * @param {*} thisArg The `this` binding of `func`.
 * @param {Array} args The arguments to invoke `func` with.
 * @returns {*} Returns the result of `func`.
 */
function apply(func, thisArg, args) {
  switch (args.length) {
    case 0: return func.call(thisArg);
    case 1: return func.call(thisArg, args[0]);
    case 2: return func.call(thisArg, args[0], args[1]);
    case 3: return func.call(thisArg, args[0], args[1], args[2]);
  }
  return func.apply(thisArg, args);
}

/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeMax = Math.max;

/**
 * A specialized version of `baseRest` which transforms the rest array.
 *
 * @private
 * @param {Function} func The function to apply a rest parameter to.
 * @param {number} [start=func.length-1] The start position of the rest parameter.
 * @param {Function} transform The rest array transform.
 * @returns {Function} Returns the new function.
 */
function overRest$1(func, start, transform) {
  start = nativeMax(start === undefined ? (func.length - 1) : start, 0);
  return function() {
    var args = arguments,
        index = -1,
        length = nativeMax(args.length - start, 0),
        array = Array(length);

    while (++index < length) {
      array[index] = args[start + index];
    }
    index = -1;
    var otherArgs = Array(start + 1);
    while (++index < start) {
      otherArgs[index] = args[index];
    }
    otherArgs[start] = transform(array);
    return apply(func, this, otherArgs);
  };
}

/**
 * This method returns the first argument it receives.
 *
 * @static
 * @since 0.1.0
 * @memberOf _
 * @category Util
 * @param {*} value Any value.
 * @returns {*} Returns `value`.
 * @example
 *
 * var object = { 'a': 1 };
 *
 * console.log(_.identity(object) === object);
 * // => true
 */
function identity(value) {
  return value;
}

// Lodash rest function without function.toString()
// remappings
function rest(func, start) {
    return overRest$1(func, start, identity);
}

var initialParams = function (fn) {
    return rest(function (args /*..., callback*/) {
        var callback = args.pop();
        fn.call(this, args, callback);
    });
};

function applyEach$1(eachfn) {
    return rest(function (fns, args) {
        var go = initialParams(function (args, callback) {
            var that = this;
            return eachfn(fns, function (fn, cb) {
                fn.apply(that, args.concat([cb]));
            }, callback);
        });
        if (args.length) {
            return go.apply(this, args);
        } else {
            return go;
        }
    });
}

/** Detect free variable `global` from Node.js. */
var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;

/** Detect free variable `self`. */
var freeSelf = typeof self == 'object' && self && self.Object === Object && self;

/** Used as a reference to the global object. */
var root = freeGlobal || freeSelf || Function('return this')();

/** Built-in value references. */
var Symbol$1 = root.Symbol;

/** Used for built-in method references. */
var objectProto = Object.prototype;

/** Used to check objects for own properties. */
var hasOwnProperty = objectProto.hasOwnProperty;

/**
 * Used to resolve the
 * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
 * of values.
 */
var nativeObjectToString = objectProto.toString;

/** Built-in value references. */
var symToStringTag$1 = Symbol$1 ? Symbol$1.toStringTag : undefined;

/**
 * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
 *
 * @private
 * @param {*} value The value to query.
 * @returns {string} Returns the raw `toStringTag`.
 */
function getRawTag(value) {
  var isOwn = hasOwnProperty.call(value, symToStringTag$1),
      tag = value[symToStringTag$1];

  try {
    value[symToStringTag$1] = undefined;
    var unmasked = true;
  } catch (e) {}

  var result = nativeObjectToString.call(value);
  if (unmasked) {
    if (isOwn) {
      value[symToStringTag$1] = tag;
    } else {
      delete value[symToStringTag$1];
    }
  }
  return result;
}

/** Used for built-in method references. */
var objectProto$1 = Object.prototype;

/**
 * Used to resolve the
 * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
 * of values.
 */
var nativeObjectToString$1 = objectProto$1.toString;

/**
 * Converts `value` to a string using `Object.prototype.toString`.
 *
 * @private
 * @param {*} value The value to convert.
 * @returns {string} Returns the converted string.
 */
function objectToString(value) {
  return nativeObjectToString$1.call(value);
}

/** `Object#toString` result references. */
var nullTag = '[object Null]';
var undefinedTag = '[object Undefined]';

/** Built-in value references. */
var symToStringTag = Symbol$1 ? Symbol$1.toStringTag : undefined;

/**
 * The base implementation of `getTag` without fallbacks for buggy environments.
 *
 * @private
 * @param {*} value The value to query.
 * @returns {string} Returns the `toStringTag`.
 */
function baseGetTag(value) {
  if (value == null) {
    return value === undefined ? undefinedTag : nullTag;
  }
  value = Object(value);
  return (symToStringTag && symToStringTag in value)
    ? getRawTag(value)
    : objectToString(value);
}

/**
 * Checks if `value` is the
 * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
 * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
 *
 * @static
 * @memberOf _
 * @since 0.1.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is an object, else `false`.
 * @example
 *
 * _.isObject({});
 * // => true
 *
 * _.isObject([1, 2, 3]);
 * // => true
 *
 * _.isObject(_.noop);
 * // => true
 *
 * _.isObject(null);
 * // => false
 */
function isObject(value) {
  var type = typeof value;
  return value != null && (type == 'object' || type == 'function');
}

/** `Object#toString` result references. */
var asyncTag = '[object AsyncFunction]';
var funcTag = '[object Function]';
var genTag = '[object GeneratorFunction]';
var proxyTag = '[object Proxy]';

/**
 * Checks if `value` is classified as a `Function` object.
 *
 * @static
 * @memberOf _
 * @since 0.1.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a function, else `false`.
 * @example
 *
 * _.isFunction(_);
 * // => true
 *
 * _.isFunction(/abc/);
 * // => false
 */
function isFunction(value) {
  if (!isObject(value)) {
    return false;
  }
  // The use of `Object#toString` avoids issues with the `typeof` operator
  // in Safari 9 which returns 'object' for typed arrays and other constructors.
  var tag = baseGetTag(value);
  return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag;
}

/** Used as references for various `Number` constants. */
var MAX_SAFE_INTEGER = 9007199254740991;

/**
 * Checks if `value` is a valid array-like length.
 *
 * **Note:** This method is loosely based on
 * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
 *
 * @static
 * @memberOf _
 * @since 4.0.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
 * @example
 *
 * _.isLength(3);
 * // => true
 *
 * _.isLength(Number.MIN_VALUE);
 * // => false
 *
 * _.isLength(Infinity);
 * // => false
 *
 * _.isLength('3');
 * // => false
 */
function isLength(value) {
  return typeof value == 'number' &&
    value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
}

/**
 * Checks if `value` is array-like. A value is considered array-like if it's
 * not a function and has a `value.length` that's an integer greater than or
 * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
 *
 * @static
 * @memberOf _
 * @since 4.0.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
 * @example
 *
 * _.isArrayLike([1, 2, 3]);
 * // => true
 *
 * _.isArrayLike(document.body.children);
 * // => true
 *
 * _.isArrayLike('abc');
 * // => true
 *
 * _.isArrayLike(_.noop);
 * // => false
 */
function isArrayLike(value) {
  return value != null && isLength(value.length) && !isFunction(value);
}

/**
 * This method returns `undefined`.
 *
 * @static
 * @memberOf _
 * @since 2.3.0
 * @category Util
 * @example
 *
 * _.times(2, _.noop);
 * // => [undefined, undefined]
 */
function noop() {
  // No operation performed.
}

function once(fn) {
    return function () {
        if (fn === null) return;
        var callFn = fn;
        fn = null;
        callFn.apply(this, arguments);
    };
}

var iteratorSymbol = typeof Symbol === 'function' && Symbol.iterator;

var getIterator = function (coll) {
    return iteratorSymbol && coll[iteratorSymbol] && coll[iteratorSymbol]();
};

/**
 * The base implementation of `_.times` without support for iteratee shorthands
 * or max array length checks.
 *
 * @private
 * @param {number} n The number of times to invoke `iteratee`.
 * @param {Function} iteratee The function invoked per iteration.
 * @returns {Array} Returns the array of results.
 */
function baseTimes(n, iteratee) {
  var index = -1,
      result = Array(n);

  while (++index < n) {
    result[index] = iteratee(index);
  }
  return result;
}

/**
 * Checks if `value` is object-like. A value is object-like if it's not `null`
 * and has a `typeof` result of "object".
 *
 * @static
 * @memberOf _
 * @since 4.0.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
 * @example
 *
 * _.isObjectLike({});
 * // => true
 *
 * _.isObjectLike([1, 2, 3]);
 * // => true
 *
 * _.isObjectLike(_.noop);
 * // => false
 *
 * _.isObjectLike(null);
 * // => false
 */
function isObjectLike(value) {
  return value != null && typeof value == 'object';
}

/** `Object#toString` result references. */
var argsTag = '[object Arguments]';

/**
 * The base implementation of `_.isArguments`.
 *
 * @private
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is an `arguments` object,
 */
function baseIsArguments(value) {
  return isObjectLike(value) && baseGetTag(value) == argsTag;
}

/** Used for built-in method references. */
var objectProto$3 = Object.prototype;

/** Used to check objects for own properties. */
var hasOwnProperty$2 = objectProto$3.hasOwnProperty;

/** Built-in value references. */
var propertyIsEnumerable = objectProto$3.propertyIsEnumerable;

/**
 * Checks if `value` is likely an `arguments` object.
 *
 * @static
 * @memberOf _
 * @since 0.1.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is an `arguments` object,
 *  else `false`.
 * @example
 *
 * _.isArguments(function() { return arguments; }());
 * // => true
 *
 * _.isArguments([1, 2, 3]);
 * // => false
 */
var isArguments = baseIsArguments(function() { return arguments; }()) ? baseIsArguments : function(value) {
  return isObjectLike(value) && hasOwnProperty$2.call(value, 'callee') &&
    !propertyIsEnumerable.call(value, 'callee');
};

/**
 * Checks if `value` is classified as an `Array` object.
 *
 * @static
 * @memberOf _
 * @since 0.1.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is an array, else `false`.
 * @example
 *
 * _.isArray([1, 2, 3]);
 * // => true
 *
 * _.isArray(document.body.children);
 * // => false
 *
 * _.isArray('abc');
 * // => false
 *
 * _.isArray(_.noop);
 * // => false
 */
var isArray = Array.isArray;

/**
 * This method returns `false`.
 *
 * @static
 * @memberOf _
 * @since 4.13.0
 * @category Util
 * @returns {boolean} Returns `false`.
 * @example
 *
 * _.times(2, _.stubFalse);
 * // => [false, false]
 */
function stubFalse() {
  return false;
}

/** Detect free variable `exports`. */
var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports;

/** Detect free variable `module`. */
var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module;

/** Detect the popular CommonJS extension `module.exports`. */
var moduleExports = freeModule && freeModule.exports === freeExports;

/** Built-in value references. */
var Buffer = moduleExports ? root.Buffer : undefined;

/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined;

/**
 * Checks if `value` is a buffer.
 *
 * @static
 * @memberOf _
 * @since 4.3.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a buffer, else `false`.
 * @example
 *
 * _.isBuffer(new Buffer(2));
 * // => true
 *
 * _.isBuffer(new Uint8Array(2));
 * // => false
 */
var isBuffer = nativeIsBuffer || stubFalse;

/** Used as references for various `Number` constants. */
var MAX_SAFE_INTEGER$1 = 9007199254740991;

/** Used to detect unsigned integer values. */
var reIsUint = /^(?:0|[1-9]\d*)$/;

/**
 * Checks if `value` is a valid array-like index.
 *
 * @private
 * @param {*} value The value to check.
 * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
 * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
 */
function isIndex(value, length) {
  length = length == null ? MAX_SAFE_INTEGER$1 : length;
  return !!length &&
    (typeof value == 'number' || reIsUint.test(value)) &&
    (value > -1 && value % 1 == 0 && value < length);
}

/** `Object#toString` result references. */
var argsTag$1 = '[object Arguments]';
var arrayTag = '[object Array]';
var boolTag = '[object Boolean]';
var dateTag = '[object Date]';
var errorTag = '[object Error]';
var funcTag$1 = '[object Function]';
var mapTag = '[object Map]';
var numberTag = '[object Number]';
var objectTag = '[object Object]';
var regexpTag = '[object RegExp]';
var setTag = '[object Set]';
var stringTag = '[object String]';
var weakMapTag = '[object WeakMap]';

var arrayBufferTag = '[object ArrayBuffer]';
var dataViewTag = '[object DataView]';
var float32Tag = '[object Float32Array]';
var float64Tag = '[object Float64Array]';
var int8Tag = '[object Int8Array]';
var int16Tag = '[object Int16Array]';
var int32Tag = '[object Int32Array]';
var uint8Tag = '[object Uint8Array]';
var uint8ClampedTag = '[object Uint8ClampedArray]';
var uint16Tag = '[object Uint16Array]';
var uint32Tag = '[object Uint32Array]';

/** Used to identify `toStringTag` values of typed arrays. */
var typedArrayTags = {};
typedArrayTags[float32Tag] = typedArrayTags[float64Tag] =
typedArrayTags[int8Tag] = typedArrayTags[int16Tag] =
typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =
typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =
typedArrayTags[uint32Tag] = true;
typedArrayTags[argsTag$1] = typedArrayTags[arrayTag] =
typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =
typedArrayTags[dataViewTag] = typedArrayTags[dateTag] =
typedArrayTags[errorTag] = typedArrayTags[funcTag$1] =
typedArrayTags[mapTag] = typedArrayTags[numberTag] =
typedArrayTags[objectTag] = typedArrayTags[regexpTag] =
typedArrayTags[setTag] = typedArrayTags[stringTag] =
typedArrayTags[weakMapTag] = false;

/**
 * The base implementation of `_.isTypedArray` without Node.js optimizations.
 *
 * @private
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
 */
function baseIsTypedArray(value) {
  return isObjectLike(value) &&
    isLength(value.length) && !!typedArrayTags[baseGetTag(value)];
}

/**
 * The base implementation of `_.unary` without support for storing metadata.
 *
 * @private
 * @param {Function} func The function to cap arguments for.
 * @returns {Function} Returns the new capped function.
 */
function baseUnary(func) {
  return function(value) {
    return func(value);
  };
}

/** Detect free variable `exports`. */
var freeExports$1 = typeof exports == 'object' && exports && !exports.nodeType && exports;

/** Detect free variable `module`. */
var freeModule$1 = freeExports$1 && typeof module == 'object' && module && !module.nodeType && module;

/** Detect the popular CommonJS extension `module.exports`. */
var moduleExports$1 = freeModule$1 && freeModule$1.exports === freeExports$1;

/** Detect free variable `process` from Node.js. */
var freeProcess = moduleExports$1 && freeGlobal.process;

/** Used to access faster Node.js helpers. */
var nodeUtil = (function() {
  try {
    return freeProcess && freeProcess.binding('util');
  } catch (e) {}
}());

/* Node.js helper references. */
var nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray;

/**
 * Checks if `value` is classified as a typed array.
 *
 * @static
 * @memberOf _
 * @since 3.0.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
 * @example
 *
 * _.isTypedArray(new Uint8Array);
 * // => true
 *
 * _.isTypedArray([]);
 * // => false
 */
var isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray;

/** Used for built-in method references. */
var objectProto$2 = Object.prototype;

/** Used to check objects for own properties. */
var hasOwnProperty$1 = objectProto$2.hasOwnProperty;

/**
 * Creates an array of the enumerable property names of the array-like `value`.
 *
 * @private
 * @param {*} value The value to query.
 * @param {boolean} inherited Specify returning inherited property names.
 * @returns {Array} Returns the array of property names.
 */
function arrayLikeKeys(value, inherited) {
  var isArr = isArray(value),
      isArg = !isArr && isArguments(value),
      isBuff = !isArr && !isArg && isBuffer(value),
      isType = !isArr && !isArg && !isBuff && isTypedArray(value),
      skipIndexes = isArr || isArg || isBuff || isType,
      result = skipIndexes ? baseTimes(value.length, String) : [],
      length = result.length;

  for (var key in value) {
    if ((inherited || hasOwnProperty$1.call(value, key)) &&
        !(skipIndexes && (
           // Safari 9 has enumerable `arguments.length` in strict mode.
           key == 'length' ||
           // Node.js 0.10 has enumerable non-index properties on buffers.
           (isBuff && (key == 'offset' || key == 'parent')) ||
           // PhantomJS 2 has enumerable non-index properties on typed arrays.
           (isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) ||
           // Skip index properties.
           isIndex(key, length)
        ))) {
      result.push(key);
    }
  }
  return result;
}

/** Used for built-in method references. */
var objectProto$5 = Object.prototype;

/**
 * Checks if `value` is likely a prototype object.
 *
 * @private
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
 */
function isPrototype(value) {
  var Ctor = value && value.constructor,
      proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto$5;

  return value === proto;
}

/**
 * Creates a unary function that invokes `func` with its argument transformed.
 *
 * @private
 * @param {Function} func The function to wrap.
 * @param {Function} transform The argument transform.
 * @returns {Function} Returns the new function.
 */
function overArg(func, transform) {
  return function(arg) {
    return func(transform(arg));
  };
}

/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeKeys = overArg(Object.keys, Object);

/** Used for built-in method references. */
var objectProto$4 = Object.prototype;

/** Used to check objects for own properties. */
var hasOwnProperty$3 = objectProto$4.hasOwnProperty;

/**
 * The base implementation of `_.keys` which doesn't treat sparse arrays as dense.
 *
 * @private
 * @param {Object} object The object to query.
 * @returns {Array} Returns the array of property names.
 */
function baseKeys(object) {
  if (!isPrototype(object)) {
    return nativeKeys(object);
  }
  var result = [];
  for (var key in Object(object)) {
    if (hasOwnProperty$3.call(object, key) && key != 'constructor') {
      result.push(key);
    }
  }
  return result;
}

/**
 * Creates an array of the own enumerable property names of `object`.
 *
 * **Note:** Non-object values are coerced to objects. See the
 * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
 * for more details.
 *
 * @static
 * @since 0.1.0
 * @memberOf _
 * @category Object
 * @param {Object} object The object to query.
 * @returns {Array} Returns the array of property names.
 * @example
 *
 * function Foo() {
 *   this.a = 1;
 *   this.b = 2;
 * }
 *
 * Foo.prototype.c = 3;
 *
 * _.keys(new Foo);
 * // => ['a', 'b'] (iteration order is not guaranteed)
 *
 * _.keys('hi');
 * // => ['0', '1']
 */
function keys(object) {
  return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object);
}

function createArrayIterator(coll) {
    var i = -1;
    var len = coll.length;
    return function next() {
        return ++i < len ? { value: coll[i], key: i } : null;
    };
}

function createES2015Iterator(iterator) {
    var i = -1;
    return function next() {
        var item = iterator.next();
        if (item.done) return null;
        i++;
        return { value: item.value, key: i };
    };
}

function createObjectIterator(obj) {
    var okeys = keys(obj);
    var i = -1;
    var len = okeys.length;
    return function next() {
        var key = okeys[++i];
        return i < len ? { value: obj[key], key: key } : null;
    };
}

function iterator(coll) {
    if (isArrayLike(coll)) {
        return createArrayIterator(coll);
    }

    var iterator = getIterator(coll);
    return iterator ? createES2015Iterator(iterator) : createObjectIterator(coll);
}

function onlyOnce(fn) {
    return function () {
        if (fn === null) throw new Error("Callback was already called.");
        var callFn = fn;
        fn = null;
        callFn.apply(this, arguments);
    };
}

// A temporary value used to identify if the loop should be broken.
// See #1064, #1293
var breakLoop = {};

function _eachOfLimit(limit) {
    return function (obj, iteratee, callback) {
        callback = once(callback || noop);
        if (limit <= 0 || !obj) {
            return callback(null);
        }
        var nextElem = iterator(obj);
        var done = false;
        var running = 0;

        function iterateeCallback(err, value) {
            running -= 1;
            if (err) {
                done = true;
                callback(err);
            } else if (value === breakLoop || done && running <= 0) {
                done = true;
                return callback(null);
            } else {
                replenish();
            }
        }

        function replenish() {
            while (running < limit && !done) {
                var elem = nextElem();
                if (elem === null) {
                    done = true;
                    if (running <= 0) {
                        callback(null);
                    }
                    return;
                }
                running += 1;
                iteratee(elem.value, elem.key, onlyOnce(iterateeCallback));
            }
        }

        replenish();
    };
}

/**
 * The same as [`eachOf`]{@link module:Collections.eachOf} but runs a maximum of `limit` async operations at a
 * time.
 *
 * @name eachOfLimit
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.eachOf]{@link module:Collections.eachOf}
 * @alias forEachOfLimit
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {Function} iteratee - A function to apply to each
 * item in `coll`. The `key` is the item's key, or index in the case of an
 * array. The iteratee is passed a `callback(err)` which must be called once it
 * has completed. If no error has occurred, the callback should be run without
 * arguments or with an explicit `null` argument. Invoked with
 * (item, key, callback).
 * @param {Function} [callback] - A callback which is called when all
 * `iteratee` functions have finished, or an error occurs. Invoked with (err).
 */
function eachOfLimit(coll, limit, iteratee, callback) {
  _eachOfLimit(limit)(coll, iteratee, callback);
}

function doLimit(fn, limit) {
    return function (iterable, iteratee, callback) {
        return fn(iterable, limit, iteratee, callback);
    };
}

// eachOf implementation optimized for array-likes
function eachOfArrayLike(coll, iteratee, callback) {
    callback = once(callback || noop);
    var index = 0,
        completed = 0,
        length = coll.length;
    if (length === 0) {
        callback(null);
    }

    function iteratorCallback(err) {
        if (err) {
            callback(err);
        } else if (++completed === length) {
            callback(null);
        }
    }

    for (; index < length; index++) {
        iteratee(coll[index], index, onlyOnce(iteratorCallback));
    }
}

// a generic version of eachOf which can handle array, object, and iterator cases.
var eachOfGeneric = doLimit(eachOfLimit, Infinity);

/**
 * Like [`each`]{@link module:Collections.each}, except that it passes the key (or index) as the second argument
 * to the iteratee.
 *
 * @name eachOf
 * @static
 * @memberOf module:Collections
 * @method
 * @alias forEachOf
 * @category Collection
 * @see [async.each]{@link module:Collections.each}
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {Function} iteratee - A function to apply to each
 * item in `coll`. The `key` is the item's key, or index in the case of an
 * array. The iteratee is passed a `callback(err)` which must be called once it
 * has completed. If no error has occurred, the callback should be run without
 * arguments or with an explicit `null` argument. Invoked with
 * (item, key, callback).
 * @param {Function} [callback] - A callback which is called when all
 * `iteratee` functions have finished, or an error occurs. Invoked with (err).
 * @example
 *
 * var obj = {dev: "/dev.json", test: "/test.json", prod: "/prod.json"};
 * var configs = {};
 *
 * async.forEachOf(obj, function (value, key, callback) {
 *     fs.readFile(__dirname + value, "utf8", function (err, data) {
 *         if (err) return callback(err);
 *         try {
 *             configs[key] = JSON.parse(data);
 *         } catch (e) {
 *             return callback(e);
 *         }
 *         callback();
 *     });
 * }, function (err) {
 *     if (err) console.error(err.message);
 *     // configs is now a map of JSON data
 *     doSomethingWith(configs);
 * });
 */
var eachOf = function (coll, iteratee, callback) {
    var eachOfImplementation = isArrayLike(coll) ? eachOfArrayLike : eachOfGeneric;
    eachOfImplementation(coll, iteratee, callback);
};

function doParallel(fn) {
    return function (obj, iteratee, callback) {
        return fn(eachOf, obj, iteratee, callback);
    };
}

function _asyncMap(eachfn, arr, iteratee, callback) {
    callback = callback || noop;
    arr = arr || [];
    var results = [];
    var counter = 0;

    eachfn(arr, function (value, _, callback) {
        var index = counter++;
        iteratee(value, function (err, v) {
            results[index] = v;
            callback(err);
        });
    }, function (err) {
        callback(err, results);
    });
}

/**
 * Produces a new collection of values by mapping each value in `coll` through
 * the `iteratee` function. The `iteratee` is called with an item from `coll`
 * and a callback for when it has finished processing. Each of these callback
 * takes 2 arguments: an `error`, and the transformed item from `coll`. If
 * `iteratee` passes an error to its callback, the main `callback` (for the
 * `map` function) is immediately called with the error.
 *
 * Note, that since this function applies the `iteratee` to each item in
 * parallel, there is no guarantee that the `iteratee` functions will complete
 * in order. However, the results array will be in the same order as the
 * original `coll`.
 *
 * If `map` is passed an Object, the results will be an Array.  The results
 * will roughly be in the order of the original Objects' keys (but this can
 * vary across JavaScript engines)
 *
 * @name map
 * @static
 * @memberOf module:Collections
 * @method
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {Function} iteratee - A function to apply to each item in `coll`.
 * The iteratee is passed a `callback(err, transformed)` which must be called
 * once it has completed with an error (which can be `null`) and a
 * transformed item. Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called when all `iteratee`
 * functions have finished, or an error occurs. Results is an Array of the
 * transformed items from the `coll`. Invoked with (err, results).
 * @example
 *
 * async.map(['file1','file2','file3'], fs.stat, function(err, results) {
 *     // results is now an array of stats for each file
 * });
 */
var map = doParallel(_asyncMap);

/**
 * Applies the provided arguments to each function in the array, calling
 * `callback` after all functions have completed. If you only provide the first
 * argument, `fns`, then it will return a function which lets you pass in the
 * arguments as if it were a single function call. If more arguments are
 * provided, `callback` is required while `args` is still optional.
 *
 * @name applyEach
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @category Control Flow
 * @param {Array|Iterable|Object} fns - A collection of asynchronous functions
 * to all call with the same arguments
 * @param {...*} [args] - any number of separate arguments to pass to the
 * function.
 * @param {Function} [callback] - the final argument should be the callback,
 * called when all functions have completed processing.
 * @returns {Function} - If only the first argument, `fns`, is provided, it will
 * return a function which lets you pass in the arguments as if it were a single
 * function call. The signature is `(..args, callback)`. If invoked with any
 * arguments, `callback` is required.
 * @example
 *
 * async.applyEach([enableSearch, updateSchema], 'bucket', callback);
 *
 * // partial application example:
 * async.each(
 *     buckets,
 *     async.applyEach([enableSearch, updateSchema]),
 *     callback
 * );
 */
var applyEach = applyEach$1(map);

function doParallelLimit(fn) {
    return function (obj, limit, iteratee, callback) {
        return fn(_eachOfLimit(limit), obj, iteratee, callback);
    };
}

/**
 * The same as [`map`]{@link module:Collections.map} but runs a maximum of `limit` async operations at a time.
 *
 * @name mapLimit
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.map]{@link module:Collections.map}
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {Function} iteratee - A function to apply to each item in `coll`.
 * The iteratee is passed a `callback(err, transformed)` which must be called
 * once it has completed with an error (which can be `null`) and a transformed
 * item. Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called when all `iteratee`
 * functions have finished, or an error occurs. Results is an array of the
 * transformed items from the `coll`. Invoked with (err, results).
 */
var mapLimit = doParallelLimit(_asyncMap);

/**
 * The same as [`map`]{@link module:Collections.map} but runs only a single async operation at a time.
 *
 * @name mapSeries
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.map]{@link module:Collections.map}
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {Function} iteratee - A function to apply to each item in `coll`.
 * The iteratee is passed a `callback(err, transformed)` which must be called
 * once it has completed with an error (which can be `null`) and a
 * transformed item. Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called when all `iteratee`
 * functions have finished, or an error occurs. Results is an array of the
 * transformed items from the `coll`. Invoked with (err, results).
 */
var mapSeries = doLimit(mapLimit, 1);

/**
 * The same as [`applyEach`]{@link module:ControlFlow.applyEach} but runs only a single async operation at a time.
 *
 * @name applyEachSeries
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.applyEach]{@link module:ControlFlow.applyEach}
 * @category Control Flow
 * @param {Array|Iterable|Object} fns - A collection of asynchronous functions to all
 * call with the same arguments
 * @param {...*} [args] - any number of separate arguments to pass to the
 * function.
 * @param {Function} [callback] - the final argument should be the callback,
 * called when all functions have completed processing.
 * @returns {Function} - If only the first argument is provided, it will return
 * a function which lets you pass in the arguments as if it were a single
 * function call.
 */
var applyEachSeries = applyEach$1(mapSeries);

/**
 * Creates a continuation function with some arguments already applied.
 *
 * Useful as a shorthand when combined with other control flow functions. Any
 * arguments passed to the returned function are added to the arguments
 * originally passed to apply.
 *
 * @name apply
 * @static
 * @memberOf module:Utils
 * @method
 * @category Util
 * @param {Function} function - The function you want to eventually apply all
 * arguments to. Invokes with (arguments...).
 * @param {...*} arguments... - Any number of arguments to automatically apply
 * when the continuation is called.
 * @example
 *
 * // using apply
 * async.parallel([
 *     async.apply(fs.writeFile, 'testfile1', 'test1'),
 *     async.apply(fs.writeFile, 'testfile2', 'test2')
 * ]);
 *
 *
 * // the same process without using apply
 * async.parallel([
 *     function(callback) {
 *         fs.writeFile('testfile1', 'test1', callback);
 *     },
 *     function(callback) {
 *         fs.writeFile('testfile2', 'test2', callback);
 *     }
 * ]);
 *
 * // It's possible to pass any number of additional arguments when calling the
 * // continuation:
 *
 * node> var fn = async.apply(sys.puts, 'one');
 * node> fn('two', 'three');
 * one
 * two
 * three
 */
var apply$2 = rest(function (fn, args) {
    return rest(function (callArgs) {
        return fn.apply(null, args.concat(callArgs));
    });
});

/**
 * Take a sync function and make it async, passing its return value to a
 * callback. This is useful for plugging sync functions into a waterfall,
 * series, or other async functions. Any arguments passed to the generated
 * function will be passed to the wrapped function (except for the final
 * callback argument). Errors thrown will be passed to the callback.
 *
 * If the function passed to `asyncify` returns a Promise, that promises's
 * resolved/rejected state will be used to call the callback, rather than simply
 * the synchronous return value.
 *
 * This also means you can asyncify ES2016 `async` functions.
 *
 * @name asyncify
 * @static
 * @memberOf module:Utils
 * @method
 * @alias wrapSync
 * @category Util
 * @param {Function} func - The synchronous function to convert to an
 * asynchronous function.
 * @returns {Function} An asynchronous wrapper of the `func`. To be invoked with
 * (callback).
 * @example
 *
 * // passing a regular synchronous function
 * async.waterfall([
 *     async.apply(fs.readFile, filename, "utf8"),
 *     async.asyncify(JSON.parse),
 *     function (data, next) {
 *         // data is the result of parsing the text.
 *         // If there was a parsing error, it would have been caught.
 *     }
 * ], callback);
 *
 * // passing a function returning a promise
 * async.waterfall([
 *     async.apply(fs.readFile, filename, "utf8"),
 *     async.asyncify(function (contents) {
 *         return db.model.create(contents);
 *     }),
 *     function (model, next) {
 *         // `model` is the instantiated model object.
 *         // If there was an error, this function would be skipped.
 *     }
 * ], callback);
 *
 * // es6 example
 * var q = async.queue(async.asyncify(async function(file) {
 *     var intermediateStep = await processFile(file);
 *     return await somePromise(intermediateStep)
 * }));
 *
 * q.push(files);
 */
function asyncify(func) {
    return initialParams(function (args, callback) {
        var result;
        try {
            result = func.apply(this, args);
        } catch (e) {
            return callback(e);
        }
        // if result is Promise object
        if (isObject(result) && typeof result.then === 'function') {
            result.then(function (value) {
                callback(null, value);
            }, function (err) {
                callback(err.message ? err : new Error(err));
            });
        } else {
            callback(null, result);
        }
    });
}

/**
 * A specialized version of `_.forEach` for arrays without support for
 * iteratee shorthands.
 *
 * @private
 * @param {Array} [array] The array to iterate over.
 * @param {Function} iteratee The function invoked per iteration.
 * @returns {Array} Returns `array`.
 */
function arrayEach(array, iteratee) {
  var index = -1,
      length = array == null ? 0 : array.length;

  while (++index < length) {
    if (iteratee(array[index], index, array) === false) {
      break;
    }
  }
  return array;
}

/**
 * Creates a base function for methods like `_.forIn` and `_.forOwn`.
 *
 * @private
 * @param {boolean} [fromRight] Specify iterating from right to left.
 * @returns {Function} Returns the new base function.
 */
function createBaseFor(fromRight) {
  return function(object, iteratee, keysFunc) {
    var index = -1,
        iterable = Object(object),
        props = keysFunc(object),
        length = props.length;

    while (length--) {
      var key = props[fromRight ? length : ++index];
      if (iteratee(iterable[key], key, iterable) === false) {
        break;
      }
    }
    return object;
  };
}

/**
 * The base implementation of `baseForOwn` which iterates over `object`
 * properties returned by `keysFunc` and invokes `iteratee` for each property.
 * Iteratee functions may exit iteration early by explicitly returning `false`.
 *
 * @private
 * @param {Object} object The object to iterate over.
 * @param {Function} iteratee The function invoked per iteration.
 * @param {Function} keysFunc The function to get the keys of `object`.
 * @returns {Object} Returns `object`.
 */
var baseFor = createBaseFor();

/**
 * The base implementation of `_.forOwn` without support for iteratee shorthands.
 *
 * @private
 * @param {Object} object The object to iterate over.
 * @param {Function} iteratee The function invoked per iteration.
 * @returns {Object} Returns `object`.
 */
function baseForOwn(object, iteratee) {
  return object && baseFor(object, iteratee, keys);
}

/**
 * The base implementation of `_.findIndex` and `_.findLastIndex` without
 * support for iteratee shorthands.
 *
 * @private
 * @param {Array} array The array to inspect.
 * @param {Function} predicate The function invoked per iteration.
 * @param {number} fromIndex The index to search from.
 * @param {boolean} [fromRight] Specify iterating from right to left.
 * @returns {number} Returns the index of the matched value, else `-1`.
 */
function baseFindIndex(array, predicate, fromIndex, fromRight) {
  var length = array.length,
      index = fromIndex + (fromRight ? 1 : -1);

  while ((fromRight ? index-- : ++index < length)) {
    if (predicate(array[index], index, array)) {
      return index;
    }
  }
  return -1;
}

/**
 * The base implementation of `_.isNaN` without support for number objects.
 *
 * @private
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
 */
function baseIsNaN(value) {
  return value !== value;
}

/**
 * A specialized version of `_.indexOf` which performs strict equality
 * comparisons of values, i.e. `===`.
 *
 * @private
 * @param {Array} array The array to inspect.
 * @param {*} value The value to search for.
 * @param {number} fromIndex The index to search from.
 * @returns {number} Returns the index of the matched value, else `-1`.
 */
function strictIndexOf(array, value, fromIndex) {
  var index = fromIndex - 1,
      length = array.length;

  while (++index < length) {
    if (array[index] === value) {
      return index;
    }
  }
  return -1;
}

/**
 * The base implementation of `_.indexOf` without `fromIndex` bounds checks.
 *
 * @private
 * @param {Array} array The array to inspect.
 * @param {*} value The value to search for.
 * @param {number} fromIndex The index to search from.
 * @returns {number} Returns the index of the matched value, else `-1`.
 */
function baseIndexOf(array, value, fromIndex) {
  return value === value
    ? strictIndexOf(array, value, fromIndex)
    : baseFindIndex(array, baseIsNaN, fromIndex);
}

/**
 * Determines the best order for running the functions in `tasks`, based on
 * their requirements. Each function can optionally depend on other functions
 * being completed first, and each function is run as soon as its requirements
 * are satisfied.
 *
 * If any of the functions pass an error to their callback, the `auto` sequence
 * will stop. Further tasks will not execute (so any other functions depending
 * on it will not run), and the main `callback` is immediately called with the
 * error.
 *
 * Functions also receive an object containing the results of functions which
 * have completed so far as the first argument, if they have dependencies. If a
 * task function has no dependencies, it will only be passed a callback.
 *
 * @name auto
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @category Control Flow
 * @param {Object} tasks - An object. Each of its properties is either a
 * function or an array of requirements, with the function itself the last item
 * in the array. The object's key of a property serves as the name of the task
 * defined by that property, i.e. can be used when specifying requirements for
 * other tasks. The function receives one or two arguments:
 * * a `results` object, containing the results of the previously executed
 *   functions, only passed if the task has any dependencies,
 * * a `callback(err, result)` function, which must be called when finished,
 *   passing an `error` (which can be `null`) and the result of the function's
 *   execution.
 * @param {number} [concurrency=Infinity] - An optional `integer` for
 * determining the maximum number of tasks that can be run in parallel. By
 * default, as many as possible.
 * @param {Function} [callback] - An optional callback which is called when all
 * the tasks have been completed. It receives the `err` argument if any `tasks`
 * pass an error to their callback. Results are always returned; however, if an
 * error occurs, no further `tasks` will be performed, and the results object
 * will only contain partial results. Invoked with (err, results).
 * @returns undefined
 * @example
 *
 * async.auto({
 *     // this function will just be passed a callback
 *     readData: async.apply(fs.readFile, 'data.txt', 'utf-8'),
 *     showData: ['readData', function(results, cb) {
 *         // results.readData is the file's contents
 *         // ...
 *     }]
 * }, callback);
 *
 * async.auto({
 *     get_data: function(callback) {
 *         console.log('in get_data');
 *         // async code to get some data
 *         callback(null, 'data', 'converted to array');
 *     },
 *     make_folder: function(callback) {
 *         console.log('in make_folder');
 *         // async code to create a directory to store a file in
 *         // this is run at the same time as getting the data
 *         callback(null, 'folder');
 *     },
 *     write_file: ['get_data', 'make_folder', function(results, callback) {
 *         console.log('in write_file', JSON.stringify(results));
 *         // once there is some data and the directory exists,
 *         // write the data to a file in the directory
 *         callback(null, 'filename');
 *     }],
 *     email_link: ['write_file', function(results, callback) {
 *         console.log('in email_link', JSON.stringify(results));
 *         // once the file is written let's email a link to it...
 *         // results.write_file contains the filename returned by write_file.
 *         callback(null, {'file':results.write_file, 'email':'user@example.com'});
 *     }]
 * }, function(err, results) {
 *     console.log('err = ', err);
 *     console.log('results = ', results);
 * });
 */
var auto = function (tasks, concurrency, callback) {
    if (typeof concurrency === 'function') {
        // concurrency is optional, shift the args.
        callback = concurrency;
        concurrency = null;
    }
    callback = once(callback || noop);
    var keys$$1 = keys(tasks);
    var numTasks = keys$$1.length;
    if (!numTasks) {
        return callback(null);
    }
    if (!concurrency) {
        concurrency = numTasks;
    }

    var results = {};
    var runningTasks = 0;
    var hasError = false;

    var listeners = {};

    var readyTasks = [];

    // for cycle detection:
    var readyToCheck = []; // tasks that have been identified as reachable
    // without the possibility of returning to an ancestor task
    var uncheckedDependencies = {};

    baseForOwn(tasks, function (task, key) {
        if (!isArray(task)) {
            // no dependencies
            enqueueTask(key, [task]);
            readyToCheck.push(key);
            return;
        }

        var dependencies = task.slice(0, task.length - 1);
        var remainingDependencies = dependencies.length;
        if (remainingDependencies === 0) {
            enqueueTask(key, task);
            readyToCheck.push(key);
            return;
        }
        uncheckedDependencies[key] = remainingDependencies;

        arrayEach(dependencies, function (dependencyName) {
            if (!tasks[dependencyName]) {
                throw new Error('async.auto task `' + key + '` has a non-existent dependency in ' + dependencies.join(', '));
            }
            addListener(dependencyName, function () {
                remainingDependencies--;
                if (remainingDependencies === 0) {
                    enqueueTask(key, task);
                }
            });
        });
    });

    checkForDeadlocks();
    processQueue();

    function enqueueTask(key, task) {
        readyTasks.push(function () {
            runTask(key, task);
        });
    }

    function processQueue() {
        if (readyTasks.length === 0 && runningTasks === 0) {
            return callback(null, results);
        }
        while (readyTasks.length && runningTasks < concurrency) {
            var run = readyTasks.shift();
            run();
        }
    }

    function addListener(taskName, fn) {
        var taskListeners = listeners[taskName];
        if (!taskListeners) {
            taskListeners = listeners[taskName] = [];
        }

        taskListeners.push(fn);
    }

    function taskComplete(taskName) {
        var taskListeners = listeners[taskName] || [];
        arrayEach(taskListeners, function (fn) {
            fn();
        });
        processQueue();
    }

    function runTask(key, task) {
        if (hasError) return;

        var taskCallback = onlyOnce(rest(function (err, args) {
            runningTasks--;
            if (args.length <= 1) {
                args = args[0];
            }
            if (err) {
                var safeResults = {};
                baseForOwn(results, function (val, rkey) {
                    safeResults[rkey] = val;
                });
                safeResults[key] = args;
                hasError = true;
                listeners = [];

                callback(err, safeResults);
            } else {
                results[key] = args;
                taskComplete(key);
            }
        }));

        runningTasks++;
        var taskFn = task[task.length - 1];
        if (task.length > 1) {
            taskFn(results, taskCallback);
        } else {
            taskFn(taskCallback);
        }
    }

    function checkForDeadlocks() {
        // Kahn's algorithm
        // https://en.wikipedia.org/wiki/Topological_sorting#Kahn.27s_algorithm
        // http://connalle.blogspot.com/2013/10/topological-sortingkahn-algorithm.html
        var currentTask;
        var counter = 0;
        while (readyToCheck.length) {
            currentTask = readyToCheck.pop();
            counter++;
            arrayEach(getDependents(currentTask), function (dependent) {
                if (--uncheckedDependencies[dependent] === 0) {
                    readyToCheck.push(dependent);
                }
            });
        }

        if (counter !== numTasks) {
            throw new Error('async.auto cannot execute tasks due to a recursive dependency');
        }
    }

    function getDependents(taskName) {
        var result = [];
        baseForOwn(tasks, function (task, key) {
            if (isArray(task) && baseIndexOf(task, taskName, 0) >= 0) {
                result.push(key);
            }
        });
        return result;
    }
};

/**
 * A specialized version of `_.map` for arrays without support for iteratee
 * shorthands.
 *
 * @private
 * @param {Array} [array] The array to iterate over.
 * @param {Function} iteratee The function invoked per iteration.
 * @returns {Array} Returns the new mapped array.
 */
function arrayMap(array, iteratee) {
  var index = -1,
      length = array == null ? 0 : array.length,
      result = Array(length);

  while (++index < length) {
    result[index] = iteratee(array[index], index, array);
  }
  return result;
}

/** `Object#toString` result references. */
var symbolTag = '[object Symbol]';

/**
 * Checks if `value` is classified as a `Symbol` primitive or object.
 *
 * @static
 * @memberOf _
 * @since 4.0.0
 * @category Lang
 * @param {*} value The value to check.
 * @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
 * @example
 *
 * _.isSymbol(Symbol.iterator);
 * // => true
 *
 * _.isSymbol('abc');
 * // => false
 */
function isSymbol(value) {
  return typeof value == 'symbol' ||
    (isObjectLike(value) && baseGetTag(value) == symbolTag);
}

/** Used as references for various `Number` constants. */
var INFINITY = 1 / 0;

/** Used to convert symbols to primitives and strings. */
var symbolProto = Symbol$1 ? Symbol$1.prototype : undefined;
var symbolToString = symbolProto ? symbolProto.toString : undefined;

/**
 * The base implementation of `_.toString` which doesn't convert nullish
 * values to empty strings.
 *
 * @private
 * @param {*} value The value to process.
 * @returns {string} Returns the string.
 */
function baseToString(value) {
  // Exit early for strings to avoid a performance hit in some environments.
  if (typeof value == 'string') {
    return value;
  }
  if (isArray(value)) {
    // Recursively convert values (susceptible to call stack limits).
    return arrayMap(value, baseToString) + '';
  }
  if (isSymbol(value)) {
    return symbolToString ? symbolToString.call(value) : '';
  }
  var result = (value + '');
  return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
}

/**
 * The base implementation of `_.slice` without an iteratee call guard.
 *
 * @private
 * @param {Array} array The array to slice.
 * @param {number} [start=0] The start position.
 * @param {number} [end=array.length] The end position.
 * @returns {Array} Returns the slice of `array`.
 */
function baseSlice(array, start, end) {
  var index = -1,
      length = array.length;

  if (start < 0) {
    start = -start > length ? 0 : (length + start);
  }
  end = end > length ? length : end;
  if (end < 0) {
    end += length;
  }
  length = start > end ? 0 : ((end - start) >>> 0);
  start >>>= 0;

  var result = Array(length);
  while (++index < length) {
    result[index] = array[index + start];
  }
  return result;
}

/**
 * Casts `array` to a slice if it's needed.
 *
 * @private
 * @param {Array} array The array to inspect.
 * @param {number} start The start position.
 * @param {number} [end=array.length] The end position.
 * @returns {Array} Returns the cast slice.
 */
function castSlice(array, start, end) {
  var length = array.length;
  end = end === undefined ? length : end;
  return (!start && end >= length) ? array : baseSlice(array, start, end);
}

/**
 * Used by `_.trim` and `_.trimEnd` to get the index of the last string symbol
 * that is not found in the character symbols.
 *
 * @private
 * @param {Array} strSymbols The string symbols to inspect.
 * @param {Array} chrSymbols The character symbols to find.
 * @returns {number} Returns the index of the last unmatched string symbol.
 */
function charsEndIndex(strSymbols, chrSymbols) {
  var index = strSymbols.length;

  while (index-- && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
  return index;
}

/**
 * Used by `_.trim` and `_.trimStart` to get the index of the first string symbol
 * that is not found in the character symbols.
 *
 * @private
 * @param {Array} strSymbols The string symbols to inspect.
 * @param {Array} chrSymbols The character symbols to find.
 * @returns {number} Returns the index of the first unmatched string symbol.
 */
function charsStartIndex(strSymbols, chrSymbols) {
  var index = -1,
      length = strSymbols.length;

  while (++index < length && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
  return index;
}

/**
 * Converts an ASCII `string` to an array.
 *
 * @private
 * @param {string} string The string to convert.
 * @returns {Array} Returns the converted array.
 */
function asciiToArray(string) {
  return string.split('');
}

/** Used to compose unicode character classes. */
var rsAstralRange = '\\ud800-\\udfff';
var rsComboMarksRange = '\\u0300-\\u036f\\ufe20-\\ufe23';
var rsComboSymbolsRange = '\\u20d0-\\u20f0';
var rsVarRange = '\\ufe0e\\ufe0f';

/** Used to compose unicode capture groups. */
var rsZWJ = '\\u200d';

/** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */
var reHasUnicode = RegExp('[' + rsZWJ + rsAstralRange  + rsComboMarksRange + rsComboSymbolsRange + rsVarRange + ']');

/**
 * Checks if `string` contains Unicode symbols.
 *
 * @private
 * @param {string} string The string to inspect.
 * @returns {boolean} Returns `true` if a symbol is found, else `false`.
 */
function hasUnicode(string) {
  return reHasUnicode.test(string);
}

/** Used to compose unicode character classes. */
var rsAstralRange$1 = '\\ud800-\\udfff';
var rsComboMarksRange$1 = '\\u0300-\\u036f\\ufe20-\\ufe23';
var rsComboSymbolsRange$1 = '\\u20d0-\\u20f0';
var rsVarRange$1 = '\\ufe0e\\ufe0f';

/** Used to compose unicode capture groups. */
var rsAstral = '[' + rsAstralRange$1 + ']';
var rsCombo = '[' + rsComboMarksRange$1 + rsComboSymbolsRange$1 + ']';
var rsFitz = '\\ud83c[\\udffb-\\udfff]';
var rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')';
var rsNonAstral = '[^' + rsAstralRange$1 + ']';
var rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}';
var rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]';
var rsZWJ$1 = '\\u200d';

/** Used to compose unicode regexes. */
var reOptMod = rsModifier + '?';
var rsOptVar = '[' + rsVarRange$1 + ']?';
var rsOptJoin = '(?:' + rsZWJ$1 + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*';
var rsSeq = rsOptVar + reOptMod + rsOptJoin;
var rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')';

/** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */
var reUnicode = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g');

/**
 * Converts a Unicode `string` to an array.
 *
 * @private
 * @param {string} string The string to convert.
 * @returns {Array} Returns the converted array.
 */
function unicodeToArray(string) {
  return string.match(reUnicode) || [];
}

/**
 * Converts `string` to an array.
 *
 * @private
 * @param {string} string The string to convert.
 * @returns {Array} Returns the converted array.
 */
function stringToArray(string) {
  return hasUnicode(string)
    ? unicodeToArray(string)
    : asciiToArray(string);
}

/**
 * Converts `value` to a string. An empty string is returned for `null`
 * and `undefined` values. The sign of `-0` is preserved.
 *
 * @static
 * @memberOf _
 * @since 4.0.0
 * @category Lang
 * @param {*} value The value to convert.
 * @returns {string} Returns the converted string.
 * @example
 *
 * _.toString(null);
 * // => ''
 *
 * _.toString(-0);
 * // => '-0'
 *
 * _.toString([1, 2, 3]);
 * // => '1,2,3'
 */
function toString(value) {
  return value == null ? '' : baseToString(value);
}

/** Used to match leading and trailing whitespace. */
var reTrim = /^\s+|\s+$/g;

/**
 * Removes leading and trailing whitespace or specified characters from `string`.
 *
 * @static
 * @memberOf _
 * @since 3.0.0
 * @category String
 * @param {string} [string=''] The string to trim.
 * @param {string} [chars=whitespace] The characters to trim.
 * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
 * @returns {string} Returns the trimmed string.
 * @example
 *
 * _.trim('  abc  ');
 * // => 'abc'
 *
 * _.trim('-_-abc-_-', '_-');
 * // => 'abc'
 *
 * _.map(['  foo  ', '  bar  '], _.trim);
 * // => ['foo', 'bar']
 */
function trim(string, chars, guard) {
  string = toString(string);
  if (string && (guard || chars === undefined)) {
    return string.replace(reTrim, '');
  }
  if (!string || !(chars = baseToString(chars))) {
    return string;
  }
  var strSymbols = stringToArray(string),
      chrSymbols = stringToArray(chars),
      start = charsStartIndex(strSymbols, chrSymbols),
      end = charsEndIndex(strSymbols, chrSymbols) + 1;

  return castSlice(strSymbols, start, end).join('');
}

var FN_ARGS = /^(function)?\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var FN_ARG = /(=.+)?(\s*)$/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;

function parseParams(func) {
    func = func.toString().replace(STRIP_COMMENTS, '');
    func = func.match(FN_ARGS)[2].replace(' ', '');
    func = func ? func.split(FN_ARG_SPLIT) : [];
    func = func.map(function (arg) {
        return trim(arg.replace(FN_ARG, ''));
    });
    return func;
}

/**
 * A dependency-injected version of the [async.auto]{@link module:ControlFlow.auto} function. Dependent
 * tasks are specified as parameters to the function, after the usual callback
 * parameter, with the parameter names matching the names of the tasks it
 * depends on. This can provide even more readable task graphs which can be
 * easier to maintain.
 *
 * If a final callback is specified, the task results are similarly injected,
 * specified as named parameters after the initial error parameter.
 *
 * The autoInject function is purely syntactic sugar and its semantics are
 * otherwise equivalent to [async.auto]{@link module:ControlFlow.auto}.
 *
 * @name autoInject
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.auto]{@link module:ControlFlow.auto}
 * @category Control Flow
 * @param {Object} tasks - An object, each of whose properties is a function of
 * the form 'func([dependencies...], callback). The object's key of a property
 * serves as the name of the task defined by that property, i.e. can be used
 * when specifying requirements for other tasks.
 * * The `callback` parameter is a `callback(err, result)` which must be called
 *   when finished, passing an `error` (which can be `null`) and the result of
 *   the function's execution. The remaining parameters name other tasks on
 *   which the task is dependent, and the results from those tasks are the
 *   arguments of those parameters.
 * @param {Function} [callback] - An optional callback which is called when all
 * the tasks have been completed. It receives the `err` argument if any `tasks`
 * pass an error to their callback, and a `results` object with any completed
 * task results, similar to `auto`.
 * @example
 *
 * //  The example from `auto` can be rewritten as follows:
 * async.autoInject({
 *     get_data: function(callback) {
 *         // async code to get some data
 *         callback(null, 'data', 'converted to array');
 *     },
 *     make_folder: function(callback) {
 *         // async code to create a directory to store a file in
 *         // this is run at the same time as getting the data
 *         callback(null, 'folder');
 *     },
 *     write_file: function(get_data, make_folder, callback) {
 *         // once there is some data and the directory exists,
 *         // write the data to a file in the directory
 *         callback(null, 'filename');
 *     },
 *     email_link: function(write_file, callback) {
 *         // once the file is written let's email a link to it...
 *         // write_file contains the filename returned by write_file.
 *         callback(null, {'file':write_file, 'email':'user@example.com'});
 *     }
 * }, function(err, results) {
 *     console.log('err = ', err);
 *     console.log('email_link = ', results.email_link);
 * });
 *
 * // If you are using a JS minifier that mangles parameter names, `autoInject`
 * // will not work with plain functions, since the parameter names will be
 * // collapsed to a single letter identifier.  To work around this, you can
 * // explicitly specify the names of the parameters your task function needs
 * // in an array, similar to Angular.js dependency injection.
 *
 * // This still has an advantage over plain `auto`, since the results a task
 * // depends on are still spread into arguments.
 * async.autoInject({
 *     //...
 *     write_file: ['get_data', 'make_folder', function(get_data, make_folder, callback) {
 *         callback(null, 'filename');
 *     }],
 *     email_link: ['write_file', function(write_file, callback) {
 *         callback(null, {'file':write_file, 'email':'user@example.com'});
 *     }]
 *     //...
 * }, function(err, results) {
 *     console.log('err = ', err);
 *     console.log('email_link = ', results.email_link);
 * });
 */
function autoInject(tasks, callback) {
    var newTasks = {};

    baseForOwn(tasks, function (taskFn, key) {
        var params;

        if (isArray(taskFn)) {
            params = taskFn.slice(0, -1);
            taskFn = taskFn[taskFn.length - 1];

            newTasks[key] = params.concat(params.length > 0 ? newTask : taskFn);
        } else if (taskFn.length === 1) {
            // no dependencies, use the function as-is
            newTasks[key] = taskFn;
        } else {
            params = parseParams(taskFn);
            if (taskFn.length === 0 && params.length === 0) {
                throw new Error("autoInject task functions require explicit parameters.");
            }

            params.pop();

            newTasks[key] = params.concat(newTask);
        }

        function newTask(results, taskCb) {
            var newArgs = arrayMap(params, function (name) {
                return results[name];
            });
            newArgs.push(taskCb);
            taskFn.apply(null, newArgs);
        }
    });

    auto(newTasks, callback);
}

var hasSetImmediate = typeof setImmediate === 'function' && setImmediate;
var hasNextTick = typeof process === 'object' && typeof process.nextTick === 'function';

function fallback(fn) {
    setTimeout(fn, 0);
}

function wrap(defer) {
    return rest(function (fn, args) {
        defer(function () {
            fn.apply(null, args);
        });
    });
}

var _defer;

if (hasSetImmediate) {
    _defer = setImmediate;
} else if (hasNextTick) {
    _defer = process.nextTick;
} else {
    _defer = fallback;
}

var setImmediate$1 = wrap(_defer);

// Simple doubly linked list (https://en.wikipedia.org/wiki/Doubly_linked_list) implementation
// used for queues. This implementation assumes that the node provided by the user can be modified
// to adjust the next and last properties. We implement only the minimal functionality
// for queue support.
function DLL() {
    this.head = this.tail = null;
    this.length = 0;
}

function setInitial(dll, node) {
    dll.length = 1;
    dll.head = dll.tail = node;
}

DLL.prototype.removeLink = function (node) {
    if (node.prev) node.prev.next = node.next;else this.head = node.next;
    if (node.next) node.next.prev = node.prev;else this.tail = node.prev;

    node.prev = node.next = null;
    this.length -= 1;
    return node;
};

DLL.prototype.empty = DLL;

DLL.prototype.insertAfter = function (node, newNode) {
    newNode.prev = node;
    newNode.next = node.next;
    if (node.next) node.next.prev = newNode;else this.tail = newNode;
    node.next = newNode;
    this.length += 1;
};

DLL.prototype.insertBefore = function (node, newNode) {
    newNode.prev = node.prev;
    newNode.next = node;
    if (node.prev) node.prev.next = newNode;else this.head = newNode;
    node.prev = newNode;
    this.length += 1;
};

DLL.prototype.unshift = function (node) {
    if (this.head) this.insertBefore(this.head, node);else setInitial(this, node);
};

DLL.prototype.push = function (node) {
    if (this.tail) this.insertAfter(this.tail, node);else setInitial(this, node);
};

DLL.prototype.shift = function () {
    return this.head && this.removeLink(this.head);
};

DLL.prototype.pop = function () {
    return this.tail && this.removeLink(this.tail);
};

function queue(worker, concurrency, payload) {
    if (concurrency == null) {
        concurrency = 1;
    } else if (concurrency === 0) {
        throw new Error('Concurrency must not be zero');
    }

    function _insert(data, insertAtFront, callback) {
        if (callback != null && typeof callback !== 'function') {
            throw new Error('task callback must be a function');
        }
        q.started = true;
        if (!isArray(data)) {
            data = [data];
        }
        if (data.length === 0 && q.idle()) {
            // call drain immediately if there are no tasks
            return setImmediate$1(function () {
                q.drain();
            });
        }

        for (var i = 0, l = data.length; i < l; i++) {
            var item = {
                data: data[i],
                callback: callback || noop
            };

            if (insertAtFront) {
                q._tasks.unshift(item);
            } else {
                q._tasks.push(item);
            }
        }
        setImmediate$1(q.process);
    }

    function _next(tasks) {
        return rest(function (args) {
            workers -= 1;

            for (var i = 0, l = tasks.length; i < l; i++) {
                var task = tasks[i];
                var index = baseIndexOf(workersList, task, 0);
                if (index >= 0) {
                    workersList.splice(index);
                }

                task.callback.apply(task, args);

                if (args[0] != null) {
                    q.error(args[0], task.data);
                }
            }

            if (workers <= q.concurrency - q.buffer) {
                q.unsaturated();
            }

            if (q.idle()) {
                q.drain();
            }
            q.process();
        });
    }

    var workers = 0;
    var workersList = [];
    var q = {
        _tasks: new DLL(),
        concurrency: concurrency,
        payload: payload,
        saturated: noop,
        unsaturated: noop,
        buffer: concurrency / 4,
        empty: noop,
        drain: noop,
        error: noop,
        started: false,
        paused: false,
        push: function (data, callback) {
            _insert(data, false, callback);
        },
        kill: function () {
            q.drain = noop;
            q._tasks.empty();
        },
        unshift: function (data, callback) {
            _insert(data, true, callback);
        },
        process: function () {
            while (!q.paused && workers < q.concurrency && q._tasks.length) {
                var tasks = [],
                    data = [];
                var l = q._tasks.length;
                if (q.payload) l = Math.min(l, q.payload);
                for (var i = 0; i < l; i++) {
                    var node = q._tasks.shift();
                    tasks.push(node);
                    data.push(node.data);
                }

                if (q._tasks.length === 0) {
                    q.empty();
                }
                workers += 1;
                workersList.push(tasks[0]);

                if (workers === q.concurrency) {
                    q.saturated();
                }

                var cb = onlyOnce(_next(tasks));
                worker(data, cb);
            }
        },
        length: function () {
            return q._tasks.length;
        },
        running: function () {
            return workers;
        },
        workersList: function () {
            return workersList;
        },
        idle: function () {
            return q._tasks.length + workers === 0;
        },
        pause: function () {
            q.paused = true;
        },
        resume: function () {
            if (q.paused === false) {
                return;
            }
            q.paused = false;
            var resumeCount = Math.min(q.concurrency, q._tasks.length);
            // Need to call q.process once per concurrent
            // worker to preserve full concurrency after pause
            for (var w = 1; w <= resumeCount; w++) {
                setImmediate$1(q.process);
            }
        }
    };
    return q;
}

/**
 * A cargo of tasks for the worker function to complete. Cargo inherits all of
 * the same methods and event callbacks as [`queue`]{@link module:ControlFlow.queue}.
 * @typedef {Object} CargoObject
 * @memberOf module:ControlFlow
 * @property {Function} length - A function returning the number of items
 * waiting to be processed. Invoke like `cargo.length()`.
 * @property {number} payload - An `integer` for determining how many tasks
 * should be process per round. This property can be changed after a `cargo` is
 * created to alter the payload on-the-fly.
 * @property {Function} push - Adds `task` to the `queue`. The callback is
 * called once the `worker` has finished processing the task. Instead of a
 * single task, an array of `tasks` can be submitted. The respective callback is
 * used for every task in the list. Invoke like `cargo.push(task, [callback])`.
 * @property {Function} saturated - A callback that is called when the
 * `queue.length()` hits the concurrency and further tasks will be queued.
 * @property {Function} empty - A callback that is called when the last item
 * from the `queue` is given to a `worker`.
 * @property {Function} drain - A callback that is called when the last item
 * from the `queue` has returned from the `worker`.
 * @property {Function} idle - a function returning false if there are items
 * waiting or being processed, or true if not. Invoke like `cargo.idle()`.
 * @property {Function} pause - a function that pauses the processing of tasks
 * until `resume()` is called. Invoke like `cargo.pause()`.
 * @property {Function} resume - a function that resumes the processing of
 * queued tasks when the queue is paused. Invoke like `cargo.resume()`.
 * @property {Function} kill - a function that removes the `drain` callback and
 * empties remaining tasks from the queue forcing it to go idle. Invoke like `cargo.kill()`.
 */

/**
 * Creates a `cargo` object with the specified payload. Tasks added to the
 * cargo will be processed altogether (up to the `payload` limit). If the
 * `worker` is in progress, the task is queued until it becomes available. Once
 * the `worker` has completed some tasks, each callback of those tasks is
 * called. Check out [these](https://camo.githubusercontent.com/6bbd36f4cf5b35a0f11a96dcd2e97711ffc2fb37/68747470733a2f2f662e636c6f75642e6769746875622e636f6d2f6173736574732f313637363837312f36383130382f62626330636662302d356632392d313165322d393734662d3333393763363464633835382e676966) [animations](https://camo.githubusercontent.com/f4810e00e1c5f5f8addbe3e9f49064fd5d102699/68747470733a2f2f662e636c6f75642e6769746875622e636f6d2f6173736574732f313637363837312f36383130312f38346339323036362d356632392d313165322d383134662d3964336430323431336266642e676966)
 * for how `cargo` and `queue` work.
 *
 * While [`queue`]{@link module:ControlFlow.queue} passes only one task to one of a group of workers
 * at a time, cargo passes an array of tasks to a single worker, repeating
 * when the worker is finished.
 *
 * @name cargo
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.queue]{@link module:ControlFlow.queue}
 * @category Control Flow
 * @param {Function} worker - An asynchronous function for processing an array
 * of queued tasks, which must call its `callback(err)` argument when finished,
 * with an optional `err` argument. Invoked with `(tasks, callback)`.
 * @param {number} [payload=Infinity] - An optional `integer` for determining
 * how many tasks should be processed per round; if omitted, the default is
 * unlimited.
 * @returns {module:ControlFlow.CargoObject} A cargo object to manage the tasks. Callbacks can
 * attached as certain properties to listen for specific events during the
 * lifecycle of the cargo and inner queue.
 * @example
 *
 * // create a cargo object with payload 2
 * var cargo = async.cargo(function(tasks, callback) {
 *     for (var i=0; i<tasks.length; i++) {
 *         console.log('hello ' + tasks[i].name);
 *     }
 *     callback();
 * }, 2);
 *
 * // add some items
 * cargo.push({name: 'foo'}, function(err) {
 *     console.log('finished processing foo');
 * });
 * cargo.push({name: 'bar'}, function(err) {
 *     console.log('finished processing bar');
 * });
 * cargo.push({name: 'baz'}, function(err) {
 *     console.log('finished processing baz');
 * });
 */
function cargo(worker, payload) {
  return queue(worker, 1, payload);
}

/**
 * The same as [`eachOf`]{@link module:Collections.eachOf} but runs only a single async operation at a time.
 *
 * @name eachOfSeries
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.eachOf]{@link module:Collections.eachOf}
 * @alias forEachOfSeries
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {Function} iteratee - A function to apply to each item in `coll`. The
 * `key` is the item's key, or index in the case of an array. The iteratee is
 * passed a `callback(err)` which must be called once it has completed. If no
 * error has occurred, the callback should be run without arguments or with an
 * explicit `null` argument. Invoked with (item, key, callback).
 * @param {Function} [callback] - A callback which is called when all `iteratee`
 * functions have finished, or an error occurs. Invoked with (err).
 */
var eachOfSeries = doLimit(eachOfLimit, 1);

/**
 * Reduces `coll` into a single value using an async `iteratee` to return each
 * successive step. `memo` is the initial state of the reduction. This function
 * only operates in series.
 *
 * For performance reasons, it may make sense to split a call to this function
 * into a parallel map, and then use the normal `Array.prototype.reduce` on the
 * results. This function is for situations where each step in the reduction
 * needs to be async; if you can get the data before reducing it, then it's
 * probably a good idea to do so.
 *
 * @name reduce
 * @static
 * @memberOf module:Collections
 * @method
 * @alias inject
 * @alias foldl
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {*} memo - The initial state of the reduction.
 * @param {Function} iteratee - A function applied to each item in the
 * array to produce the next step in the reduction. The `iteratee` is passed a
 * `callback(err, reduction)` which accepts an optional error as its first
 * argument, and the state of the reduction as the second. If an error is
 * passed to the callback, the reduction is stopped and the main `callback` is
 * immediately called with the error. Invoked with (memo, item, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Result is the reduced value. Invoked with
 * (err, result).
 * @example
 *
 * async.reduce([1,2,3], 0, function(memo, item, callback) {
 *     // pointless async:
 *     process.nextTick(function() {
 *         callback(null, memo + item)
 *     });
 * }, function(err, result) {
 *     // result is now equal to the last value of memo, which is 6
 * });
 */
function reduce(coll, memo, iteratee, callback) {
    callback = once(callback || noop);
    eachOfSeries(coll, function (x, i, callback) {
        iteratee(memo, x, function (err, v) {
            memo = v;
            callback(err);
        });
    }, function (err) {
        callback(err, memo);
    });
}

/**
 * Version of the compose function that is more natural to read. Each function
 * consumes the return value of the previous function. It is the equivalent of
 * [compose]{@link module:ControlFlow.compose} with the arguments reversed.
 *
 * Each function is executed with the `this` binding of the composed function.
 *
 * @name seq
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.compose]{@link module:ControlFlow.compose}
 * @category Control Flow
 * @param {...Function} functions - the asynchronous functions to compose
 * @returns {Function} a function that composes the `functions` in order
 * @example
 *
 * // Requires lodash (or underscore), express3 and dresende's orm2.
 * // Part of an app, that fetches cats of the logged user.
 * // This example uses `seq` function to avoid overnesting and error
 * // handling clutter.
 * app.get('/cats', function(request, response) {
 *     var User = request.models.User;
 *     async.seq(
 *         _.bind(User.get, User),  // 'User.get' has signature (id, callback(err, data))
 *         function(user, fn) {
 *             user.getCats(fn);      // 'getCats' has signature (callback(err, data))
 *         }
 *     )(req.session.user_id, function (err, cats) {
 *         if (err) {
 *             console.error(err);
 *             response.json({ status: 'error', message: err.message });
 *         } else {
 *             response.json({ status: 'ok', message: 'Cats found', data: cats });
 *         }
 *     });
 * });
 */
var seq$1 = rest(function seq(functions) {
    return rest(function (args) {
        var that = this;

        var cb = args[args.length - 1];
        if (typeof cb == 'function') {
            args.pop();
        } else {
            cb = noop;
        }

        reduce(functions, args, function (newargs, fn, cb) {
            fn.apply(that, newargs.concat([rest(function (err, nextargs) {
                cb(err, nextargs);
            })]));
        }, function (err, results) {
            cb.apply(that, [err].concat(results));
        });
    });
});

/**
 * Creates a function which is a composition of the passed asynchronous
 * functions. Each function consumes the return value of the function that
 * follows. Composing functions `f()`, `g()`, and `h()` would produce the result
 * of `f(g(h()))`, only this version uses callbacks to obtain the return values.
 *
 * Each function is executed with the `this` binding of the composed function.
 *
 * @name compose
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @category Control Flow
 * @param {...Function} functions - the asynchronous functions to compose
 * @returns {Function} an asynchronous function that is the composed
 * asynchronous `functions`
 * @example
 *
 * function add1(n, callback) {
 *     setTimeout(function () {
 *         callback(null, n + 1);
 *     }, 10);
 * }
 *
 * function mul3(n, callback) {
 *     setTimeout(function () {
 *         callback(null, n * 3);
 *     }, 10);
 * }
 *
 * var add1mul3 = async.compose(mul3, add1);
 * add1mul3(4, function (err, result) {
 *     // result now equals 15
 * });
 */
var compose = rest(function (args) {
  return seq$1.apply(null, args.reverse());
});

function concat$1(eachfn, arr, fn, callback) {
    var result = [];
    eachfn(arr, function (x, index, cb) {
        fn(x, function (err, y) {
            result = result.concat(y || []);
            cb(err);
        });
    }, function (err) {
        callback(err, result);
    });
}

/**
 * Applies `iteratee` to each item in `coll`, concatenating the results. Returns
 * the concatenated list. The `iteratee`s are called in parallel, and the
 * results are concatenated as they return. There is no guarantee that the
 * results array will be returned in the original order of `coll` passed to the
 * `iteratee` function.
 *
 * @name concat
 * @static
 * @memberOf module:Collections
 * @method
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {Function} iteratee - A function to apply to each item in `coll`.
 * The iteratee is passed a `callback(err, results)` which must be called once
 * it has completed with an error (which can be `null`) and an array of results.
 * Invoked with (item, callback).
 * @param {Function} [callback(err)] - A callback which is called after all the
 * `iteratee` functions have finished, or an error occurs. Results is an array
 * containing the concatenated results of the `iteratee` function. Invoked with
 * (err, results).
 * @example
 *
 * async.concat(['dir1','dir2','dir3'], fs.readdir, function(err, files) {
 *     // files is now a list of filenames that exist in the 3 directories
 * });
 */
var concat = doParallel(concat$1);

function doSeries(fn) {
    return function (obj, iteratee, callback) {
        return fn(eachOfSeries, obj, iteratee, callback);
    };
}

/**
 * The same as [`concat`]{@link module:Collections.concat} but runs only a single async operation at a time.
 *
 * @name concatSeries
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.concat]{@link module:Collections.concat}
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {Function} iteratee - A function to apply to each item in `coll`.
 * The iteratee is passed a `callback(err, results)` which must be called once
 * it has completed with an error (which can be `null`) and an array of results.
 * Invoked with (item, callback).
 * @param {Function} [callback(err)] - A callback which is called after all the
 * `iteratee` functions have finished, or an error occurs. Results is an array
 * containing the concatenated results of the `iteratee` function. Invoked with
 * (err, results).
 */
var concatSeries = doSeries(concat$1);

/**
 * Returns a function that when called, calls-back with the values provided.
 * Useful as the first function in a [`waterfall`]{@link module:ControlFlow.waterfall}, or for plugging values in to
 * [`auto`]{@link module:ControlFlow.auto}.
 *
 * @name constant
 * @static
 * @memberOf module:Utils
 * @method
 * @category Util
 * @param {...*} arguments... - Any number of arguments to automatically invoke
 * callback with.
 * @returns {Function} Returns a function that when invoked, automatically
 * invokes the callback with the previous given arguments.
 * @example
 *
 * async.waterfall([
 *     async.constant(42),
 *     function (value, next) {
 *         // value === 42
 *     },
 *     //...
 * ], callback);
 *
 * async.waterfall([
 *     async.constant(filename, "utf8"),
 *     fs.readFile,
 *     function (fileData, next) {
 *         //...
 *     }
 *     //...
 * ], callback);
 *
 * async.auto({
 *     hostname: async.constant("https://server.net/"),
 *     port: findFreePort,
 *     launchServer: ["hostname", "port", function (options, cb) {
 *         startServer(options, cb);
 *     }],
 *     //...
 * }, callback);
 */
var constant = rest(function (values) {
    var args = [null].concat(values);
    return initialParams(function (ignoredArgs, callback) {
        return callback.apply(this, args);
    });
});

function _createTester(eachfn, check, getResult) {
    return function (arr, limit, iteratee, cb) {
        function done() {
            if (cb) {
                cb(null, getResult(false));
            }
        }
        function wrappedIteratee(x, _, callback) {
            if (!cb) return callback();
            iteratee(x, function (err, v) {
                // Check cb as another iteratee may have resolved with a
                // value or error since we started this iteratee
                if (cb && (err || check(v))) {
                    if (err) cb(err);else cb(err, getResult(true, x));
                    cb = iteratee = false;
                    callback(err, breakLoop);
                } else {
                    callback();
                }
            });
        }
        if (arguments.length > 3) {
            cb = cb || noop;
            eachfn(arr, limit, wrappedIteratee, done);
        } else {
            cb = iteratee;
            cb = cb || noop;
            iteratee = limit;
            eachfn(arr, wrappedIteratee, done);
        }
    };
}

function _findGetResult(v, x) {
    return x;
}

/**
 * Returns the first value in `coll` that passes an async truth test. The
 * `iteratee` is applied in parallel, meaning the first iteratee to return
 * `true` will fire the detect `callback` with that result. That means the
 * result might not be the first item in the original `coll` (in terms of order)
 * that passes the test.

 * If order within the original `coll` is important, then look at
 * [`detectSeries`]{@link module:Collections.detectSeries}.
 *
 * @name detect
 * @static
 * @memberOf module:Collections
 * @method
 * @alias find
 * @category Collections
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {Function} iteratee - A truth test to apply to each item in `coll`.
 * The iteratee is passed a `callback(err, truthValue)` which must be called
 * with a boolean argument once it has completed. Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called as soon as any
 * iteratee returns `true`, or after all the `iteratee` functions have finished.
 * Result will be the first item in the array that passes the truth test
 * (iteratee) or the value `undefined` if none passed. Invoked with
 * (err, result).
 * @example
 *
 * async.detect(['file1','file2','file3'], function(filePath, callback) {
 *     fs.access(filePath, function(err) {
 *         callback(null, !err)
 *     });
 * }, function(err, result) {
 *     // result now equals the first file in the list that exists
 * });
 */
var detect = _createTester(eachOf, identity, _findGetResult);

/**
 * The same as [`detect`]{@link module:Collections.detect} but runs a maximum of `limit` async operations at a
 * time.
 *
 * @name detectLimit
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.detect]{@link module:Collections.detect}
 * @alias findLimit
 * @category Collections
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {Function} iteratee - A truth test to apply to each item in `coll`.
 * The iteratee is passed a `callback(err, truthValue)` which must be called
 * with a boolean argument once it has completed. Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called as soon as any
 * iteratee returns `true`, or after all the `iteratee` functions have finished.
 * Result will be the first item in the array that passes the truth test
 * (iteratee) or the value `undefined` if none passed. Invoked with
 * (err, result).
 */
var detectLimit = _createTester(eachOfLimit, identity, _findGetResult);

/**
 * The same as [`detect`]{@link module:Collections.detect} but runs only a single async operation at a time.
 *
 * @name detectSeries
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.detect]{@link module:Collections.detect}
 * @alias findSeries
 * @category Collections
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {Function} iteratee - A truth test to apply to each item in `coll`.
 * The iteratee is passed a `callback(err, truthValue)` which must be called
 * with a boolean argument once it has completed. Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called as soon as any
 * iteratee returns `true`, or after all the `iteratee` functions have finished.
 * Result will be the first item in the array that passes the truth test
 * (iteratee) or the value `undefined` if none passed. Invoked with
 * (err, result).
 */
var detectSeries = _createTester(eachOfSeries, identity, _findGetResult);

function consoleFunc(name) {
    return rest(function (fn, args) {
        fn.apply(null, args.concat([rest(function (err, args) {
            if (typeof console === 'object') {
                if (err) {
                    if (console.error) {
                        console.error(err);
                    }
                } else if (console[name]) {
                    arrayEach(args, function (x) {
                        console[name](x);
                    });
                }
            }
        })]));
    });
}

/**
 * Logs the result of an `async` function to the `console` using `console.dir`
 * to display the properties of the resulting object. Only works in Node.js or
 * in browsers that support `console.dir` and `console.error` (such as FF and
 * Chrome). If multiple arguments are returned from the async function,
 * `console.dir` is called on each argument in order.
 *
 * @name dir
 * @static
 * @memberOf module:Utils
 * @method
 * @category Util
 * @param {Function} function - The function you want to eventually apply all
 * arguments to.
 * @param {...*} arguments... - Any number of arguments to apply to the function.
 * @example
 *
 * // in a module
 * var hello = function(name, callback) {
 *     setTimeout(function() {
 *         callback(null, {hello: name});
 *     }, 1000);
 * };
 *
 * // in the node repl
 * node> async.dir(hello, 'world');
 * {hello: 'world'}
 */
var dir = consoleFunc('dir');

/**
 * The post-check version of [`during`]{@link module:ControlFlow.during}. To reflect the difference in
 * the order of operations, the arguments `test` and `fn` are switched.
 *
 * Also a version of [`doWhilst`]{@link module:ControlFlow.doWhilst} with asynchronous `test` function.
 * @name doDuring
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.during]{@link module:ControlFlow.during}
 * @category Control Flow
 * @param {Function} fn - A function which is called each time `test` passes.
 * The function is passed a `callback(err)`, which must be called once it has
 * completed with an optional `err` argument. Invoked with (callback).
 * @param {Function} test - asynchronous truth test to perform before each
 * execution of `fn`. Invoked with (...args, callback), where `...args` are the
 * non-error args from the previous callback of `fn`.
 * @param {Function} [callback] - A callback which is called after the test
 * function has failed and repeated execution of `fn` has stopped. `callback`
 * will be passed an error if one occured, otherwise `null`.
 */
function doDuring(fn, test, callback) {
    callback = onlyOnce(callback || noop);

    var next = rest(function (err, args) {
        if (err) return callback(err);
        args.push(check);
        test.apply(this, args);
    });

    function check(err, truth) {
        if (err) return callback(err);
        if (!truth) return callback(null);
        fn(next);
    }

    check(null, true);
}

/**
 * The post-check version of [`whilst`]{@link module:ControlFlow.whilst}. To reflect the difference in
 * the order of operations, the arguments `test` and `iteratee` are switched.
 *
 * `doWhilst` is to `whilst` as `do while` is to `while` in plain JavaScript.
 *
 * @name doWhilst
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.whilst]{@link module:ControlFlow.whilst}
 * @category Control Flow
 * @param {Function} iteratee - A function which is called each time `test`
 * passes. The function is passed a `callback(err)`, which must be called once
 * it has completed with an optional `err` argument. Invoked with (callback).
 * @param {Function} test - synchronous truth test to perform after each
 * execution of `iteratee`. Invoked with the non-error callback results of 
 * `iteratee`.
 * @param {Function} [callback] - A callback which is called after the test
 * function has failed and repeated execution of `iteratee` has stopped.
 * `callback` will be passed an error and any arguments passed to the final
 * `iteratee`'s callback. Invoked with (err, [results]);
 */
function doWhilst(iteratee, test, callback) {
    callback = onlyOnce(callback || noop);
    var next = rest(function (err, args) {
        if (err) return callback(err);
        if (test.apply(this, args)) return iteratee(next);
        callback.apply(null, [null].concat(args));
    });
    iteratee(next);
}

/**
 * Like ['doWhilst']{@link module:ControlFlow.doWhilst}, except the `test` is inverted. Note the
 * argument ordering differs from `until`.
 *
 * @name doUntil
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.doWhilst]{@link module:ControlFlow.doWhilst}
 * @category Control Flow
 * @param {Function} fn - A function which is called each time `test` fails.
 * The function is passed a `callback(err)`, which must be called once it has
 * completed with an optional `err` argument. Invoked with (callback).
 * @param {Function} test - synchronous truth test to perform after each
 * execution of `fn`. Invoked with the non-error callback results of `fn`.
 * @param {Function} [callback] - A callback which is called after the test
 * function has passed and repeated execution of `fn` has stopped. `callback`
 * will be passed an error and any arguments passed to the final `fn`'s
 * callback. Invoked with (err, [results]);
 */
function doUntil(fn, test, callback) {
    doWhilst(fn, function () {
        return !test.apply(this, arguments);
    }, callback);
}

/**
 * Like [`whilst`]{@link module:ControlFlow.whilst}, except the `test` is an asynchronous function that
 * is passed a callback in the form of `function (err, truth)`. If error is
 * passed to `test` or `fn`, the main callback is immediately called with the
 * value of the error.
 *
 * @name during
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.whilst]{@link module:ControlFlow.whilst}
 * @category Control Flow
 * @param {Function} test - asynchronous truth test to perform before each
 * execution of `fn`. Invoked with (callback).
 * @param {Function} fn - A function which is called each time `test` passes.
 * The function is passed a `callback(err)`, which must be called once it has
 * completed with an optional `err` argument. Invoked with (callback).
 * @param {Function} [callback] - A callback which is called after the test
 * function has failed and repeated execution of `fn` has stopped. `callback`
 * will be passed an error, if one occured, otherwise `null`.
 * @example
 *
 * var count = 0;
 *
 * async.during(
 *     function (callback) {
 *         return callback(null, count < 5);
 *     },
 *     function (callback) {
 *         count++;
 *         setTimeout(callback, 1000);
 *     },
 *     function (err) {
 *         // 5 seconds have passed
 *     }
 * );
 */
function during(test, fn, callback) {
    callback = onlyOnce(callback || noop);

    function next(err) {
        if (err) return callback(err);
        test(check);
    }

    function check(err, truth) {
        if (err) return callback(err);
        if (!truth) return callback(null);
        fn(next);
    }

    test(check);
}

function _withoutIndex(iteratee) {
    return function (value, index, callback) {
        return iteratee(value, callback);
    };
}

/**
 * Applies the function `iteratee` to each item in `coll`, in parallel.
 * The `iteratee` is called with an item from the list, and a callback for when
 * it has finished. If the `iteratee` passes an error to its `callback`, the
 * main `callback` (for the `each` function) is immediately called with the
 * error.
 *
 * Note, that since this function applies `iteratee` to each item in parallel,
 * there is no guarantee that the iteratee functions will complete in order.
 *
 * @name each
 * @static
 * @memberOf module:Collections
 * @method
 * @alias forEach
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {Function} iteratee - A function to apply to each item
 * in `coll`. The iteratee is passed a `callback(err)` which must be called once
 * it has completed. If no error has occurred, the `callback` should be run
 * without arguments or with an explicit `null` argument. The array index is not
 * passed to the iteratee. Invoked with (item, callback). If you need the index,
 * use `eachOf`.
 * @param {Function} [callback] - A callback which is called when all
 * `iteratee` functions have finished, or an error occurs. Invoked with (err).
 * @example
 *
 * // assuming openFiles is an array of file names and saveFile is a function
 * // to save the modified contents of that file:
 *
 * async.each(openFiles, saveFile, function(err){
 *   // if any of the saves produced an error, err would equal that error
 * });
 *
 * // assuming openFiles is an array of file names
 * async.each(openFiles, function(file, callback) {
 *
 *     // Perform operation on file here.
 *     console.log('Processing file ' + file);
 *
 *     if( file.length > 32 ) {
 *       console.log('This file name is too long');
 *       callback('File name too long');
 *     } else {
 *       // Do work to process file here
 *       console.log('File processed');
 *       callback();
 *     }
 * }, function(err) {
 *     // if any of the file processing produced an error, err would equal that error
 *     if( err ) {
 *       // One of the iterations produced an error.
 *       // All processing will now stop.
 *       console.log('A file failed to process');
 *     } else {
 *       console.log('All files have been processed successfully');
 *     }
 * });
 */
function eachLimit(coll, iteratee, callback) {
  eachOf(coll, _withoutIndex(iteratee), callback);
}

/**
 * The same as [`each`]{@link module:Collections.each} but runs a maximum of `limit` async operations at a time.
 *
 * @name eachLimit
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.each]{@link module:Collections.each}
 * @alias forEachLimit
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {Function} iteratee - A function to apply to each item in `coll`. The
 * iteratee is passed a `callback(err)` which must be called once it has
 * completed. If no error has occurred, the `callback` should be run without
 * arguments or with an explicit `null` argument. The array index is not passed
 * to the iteratee. Invoked with (item, callback). If you need the index, use
 * `eachOfLimit`.
 * @param {Function} [callback] - A callback which is called when all
 * `iteratee` functions have finished, or an error occurs. Invoked with (err).
 */
function eachLimit$1(coll, limit, iteratee, callback) {
  _eachOfLimit(limit)(coll, _withoutIndex(iteratee), callback);
}

/**
 * The same as [`each`]{@link module:Collections.each} but runs only a single async operation at a time.
 *
 * @name eachSeries
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.each]{@link module:Collections.each}
 * @alias forEachSeries
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {Function} iteratee - A function to apply to each
 * item in `coll`. The iteratee is passed a `callback(err)` which must be called
 * once it has completed. If no error has occurred, the `callback` should be run
 * without arguments or with an explicit `null` argument. The array index is
 * not passed to the iteratee. Invoked with (item, callback). If you need the
 * index, use `eachOfSeries`.
 * @param {Function} [callback] - A callback which is called when all
 * `iteratee` functions have finished, or an error occurs. Invoked with (err).
 */
var eachSeries = doLimit(eachLimit$1, 1);

/**
 * Wrap an async function and ensure it calls its callback on a later tick of
 * the event loop.  If the function already calls its callback on a next tick,
 * no extra deferral is added. This is useful for preventing stack overflows
 * (`RangeError: Maximum call stack size exceeded`) and generally keeping
 * [Zalgo](http://blog.izs.me/post/59142742143/designing-apis-for-asynchrony)
 * contained.
 *
 * @name ensureAsync
 * @static
 * @memberOf module:Utils
 * @method
 * @category Util
 * @param {Function} fn - an async function, one that expects a node-style
 * callback as its last argument.
 * @returns {Function} Returns a wrapped function with the exact same call
 * signature as the function passed in.
 * @example
 *
 * function sometimesAsync(arg, callback) {
 *     if (cache[arg]) {
 *         return callback(null, cache[arg]); // this would be synchronous!!
 *     } else {
 *         doSomeIO(arg, callback); // this IO would be asynchronous
 *     }
 * }
 *
 * // this has a risk of stack overflows if many results are cached in a row
 * async.mapSeries(args, sometimesAsync, done);
 *
 * // this will defer sometimesAsync's callback if necessary,
 * // preventing stack overflows
 * async.mapSeries(args, async.ensureAsync(sometimesAsync), done);
 */
function ensureAsync(fn) {
    return initialParams(function (args, callback) {
        var sync = true;
        args.push(function () {
            var innerArgs = arguments;
            if (sync) {
                setImmediate$1(function () {
                    callback.apply(null, innerArgs);
                });
            } else {
                callback.apply(null, innerArgs);
            }
        });
        fn.apply(this, args);
        sync = false;
    });
}

function notId(v) {
    return !v;
}

/**
 * Returns `true` if every element in `coll` satisfies an async test. If any
 * iteratee call returns `false`, the main `callback` is immediately called.
 *
 * @name every
 * @static
 * @memberOf module:Collections
 * @method
 * @alias all
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {Function} iteratee - A truth test to apply to each item in the
 * collection in parallel. The iteratee is passed a `callback(err, truthValue)`
 * which must be called with a  boolean argument once it has completed. Invoked
 * with (item, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Result will be either `true` or `false`
 * depending on the values of the async tests. Invoked with (err, result).
 * @example
 *
 * async.every(['file1','file2','file3'], function(filePath, callback) {
 *     fs.access(filePath, function(err) {
 *         callback(null, !err)
 *     });
 * }, function(err, result) {
 *     // if result is true then every file exists
 * });
 */
var every = _createTester(eachOf, notId, notId);

/**
 * The same as [`every`]{@link module:Collections.every} but runs a maximum of `limit` async operations at a time.
 *
 * @name everyLimit
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.every]{@link module:Collections.every}
 * @alias allLimit
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {Function} iteratee - A truth test to apply to each item in the
 * collection in parallel. The iteratee is passed a `callback(err, truthValue)`
 * which must be called with a  boolean argument once it has completed. Invoked
 * with (item, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Result will be either `true` or `false`
 * depending on the values of the async tests. Invoked with (err, result).
 */
var everyLimit = _createTester(eachOfLimit, notId, notId);

/**
 * The same as [`every`]{@link module:Collections.every} but runs only a single async operation at a time.
 *
 * @name everySeries
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.every]{@link module:Collections.every}
 * @alias allSeries
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {Function} iteratee - A truth test to apply to each item in the
 * collection in parallel. The iteratee is passed a `callback(err, truthValue)`
 * which must be called with a  boolean argument once it has completed. Invoked
 * with (item, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Result will be either `true` or `false`
 * depending on the values of the async tests. Invoked with (err, result).
 */
var everySeries = doLimit(everyLimit, 1);

/**
 * The base implementation of `_.property` without support for deep paths.
 *
 * @private
 * @param {string} key The key of the property to get.
 * @returns {Function} Returns the new accessor function.
 */
function baseProperty(key) {
  return function(object) {
    return object == null ? undefined : object[key];
  };
}

function filterArray(eachfn, arr, iteratee, callback) {
    var truthValues = new Array(arr.length);
    eachfn(arr, function (x, index, callback) {
        iteratee(x, function (err, v) {
            truthValues[index] = !!v;
            callback(err);
        });
    }, function (err) {
        if (err) return callback(err);
        var results = [];
        for (var i = 0; i < arr.length; i++) {
            if (truthValues[i]) results.push(arr[i]);
        }
        callback(null, results);
    });
}

function filterGeneric(eachfn, coll, iteratee, callback) {
    var results = [];
    eachfn(coll, function (x, index, callback) {
        iteratee(x, function (err, v) {
            if (err) {
                callback(err);
            } else {
                if (v) {
                    results.push({ index: index, value: x });
                }
                callback();
            }
        });
    }, function (err) {
        if (err) {
            callback(err);
        } else {
            callback(null, arrayMap(results.sort(function (a, b) {
                return a.index - b.index;
            }), baseProperty('value')));
        }
    });
}

function _filter(eachfn, coll, iteratee, callback) {
    var filter = isArrayLike(coll) ? filterArray : filterGeneric;
    filter(eachfn, coll, iteratee, callback || noop);
}

/**
 * Returns a new array of all the values in `coll` which pass an async truth
 * test. This operation is performed in parallel, but the results array will be
 * in the same order as the original.
 *
 * @name filter
 * @static
 * @memberOf module:Collections
 * @method
 * @alias select
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {Function} iteratee - A truth test to apply to each item in `coll`.
 * The `iteratee` is passed a `callback(err, truthValue)`, which must be called
 * with a boolean argument once it has completed. Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Invoked with (err, results).
 * @example
 *
 * async.filter(['file1','file2','file3'], function(filePath, callback) {
 *     fs.access(filePath, function(err) {
 *         callback(null, !err)
 *     });
 * }, function(err, results) {
 *     // results now equals an array of the existing files
 * });
 */
var filter = doParallel(_filter);

/**
 * The same as [`filter`]{@link module:Collections.filter} but runs a maximum of `limit` async operations at a
 * time.
 *
 * @name filterLimit
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.filter]{@link module:Collections.filter}
 * @alias selectLimit
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {Function} iteratee - A truth test to apply to each item in `coll`.
 * The `iteratee` is passed a `callback(err, truthValue)`, which must be called
 * with a boolean argument once it has completed. Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Invoked with (err, results).
 */
var filterLimit = doParallelLimit(_filter);

/**
 * The same as [`filter`]{@link module:Collections.filter} but runs only a single async operation at a time.
 *
 * @name filterSeries
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.filter]{@link module:Collections.filter}
 * @alias selectSeries
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {Function} iteratee - A truth test to apply to each item in `coll`.
 * The `iteratee` is passed a `callback(err, truthValue)`, which must be called
 * with a boolean argument once it has completed. Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Invoked with (err, results)
 */
var filterSeries = doLimit(filterLimit, 1);

/**
 * Calls the asynchronous function `fn` with a callback parameter that allows it
 * to call itself again, in series, indefinitely.

 * If an error is passed to the
 * callback then `errback` is called with the error, and execution stops,
 * otherwise it will never be called.
 *
 * @name forever
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @category Control Flow
 * @param {Function} fn - a function to call repeatedly. Invoked with (next).
 * @param {Function} [errback] - when `fn` passes an error to it's callback,
 * this function will be called, and execution stops. Invoked with (err).
 * @example
 *
 * async.forever(
 *     function(next) {
 *         // next is suitable for passing to things that need a callback(err [, whatever]);
 *         // it will result in this function being called again.
 *     },
 *     function(err) {
 *         // if next is called with a value in its first parameter, it will appear
 *         // in here as 'err', and execution will stop.
 *     }
 * );
 */
function forever(fn, errback) {
    var done = onlyOnce(errback || noop);
    var task = ensureAsync(fn);

    function next(err) {
        if (err) return done(err);
        task(next);
    }
    next();
}

/**
 * Logs the result of an `async` function to the `console`. Only works in
 * Node.js or in browsers that support `console.log` and `console.error` (such
 * as FF and Chrome). If multiple arguments are returned from the async
 * function, `console.log` is called on each argument in order.
 *
 * @name log
 * @static
 * @memberOf module:Utils
 * @method
 * @category Util
 * @param {Function} function - The function you want to eventually apply all
 * arguments to.
 * @param {...*} arguments... - Any number of arguments to apply to the function.
 * @example
 *
 * // in a module
 * var hello = function(name, callback) {
 *     setTimeout(function() {
 *         callback(null, 'hello ' + name);
 *     }, 1000);
 * };
 *
 * // in the node repl
 * node> async.log(hello, 'world');
 * 'hello world'
 */
var log = consoleFunc('log');

/**
 * The same as [`mapValues`]{@link module:Collections.mapValues} but runs a maximum of `limit` async operations at a
 * time.
 *
 * @name mapValuesLimit
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.mapValues]{@link module:Collections.mapValues}
 * @category Collection
 * @param {Object} obj - A collection to iterate over.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {Function} iteratee - A function to apply to each value in `obj`.
 * The iteratee is passed a `callback(err, transformed)` which must be called
 * once it has completed with an error (which can be `null`) and a
 * transformed value. Invoked with (value, key, callback).
 * @param {Function} [callback] - A callback which is called when all `iteratee`
 * functions have finished, or an error occurs. `result` is a new object consisting
 * of each key from `obj`, with each transformed value on the right-hand side.
 * Invoked with (err, result).
 */
function mapValuesLimit(obj, limit, iteratee, callback) {
    callback = once(callback || noop);
    var newObj = {};
    eachOfLimit(obj, limit, function (val, key, next) {
        iteratee(val, key, function (err, result) {
            if (err) return next(err);
            newObj[key] = result;
            next();
        });
    }, function (err) {
        callback(err, newObj);
    });
}

/**
 * A relative of [`map`]{@link module:Collections.map}, designed for use with objects.
 *
 * Produces a new Object by mapping each value of `obj` through the `iteratee`
 * function. The `iteratee` is called each `value` and `key` from `obj` and a
 * callback for when it has finished processing. Each of these callbacks takes
 * two arguments: an `error`, and the transformed item from `obj`. If `iteratee`
 * passes an error to its callback, the main `callback` (for the `mapValues`
 * function) is immediately called with the error.
 *
 * Note, the order of the keys in the result is not guaranteed.  The keys will
 * be roughly in the order they complete, (but this is very engine-specific)
 *
 * @name mapValues
 * @static
 * @memberOf module:Collections
 * @method
 * @category Collection
 * @param {Object} obj - A collection to iterate over.
 * @param {Function} iteratee - A function to apply to each value and key in
 * `coll`. The iteratee is passed a `callback(err, transformed)` which must be
 * called once it has completed with an error (which can be `null`) and a
 * transformed value. Invoked with (value, key, callback).
 * @param {Function} [callback] - A callback which is called when all `iteratee`
 * functions have finished, or an error occurs. `result` is a new object consisting
 * of each key from `obj`, with each transformed value on the right-hand side.
 * Invoked with (err, result).
 * @example
 *
 * async.mapValues({
 *     f1: 'file1',
 *     f2: 'file2',
 *     f3: 'file3'
 * }, function (file, key, callback) {
 *   fs.stat(file, callback);
 * }, function(err, result) {
 *     // result is now a map of stats for each file, e.g.
 *     // {
 *     //     f1: [stats for file1],
 *     //     f2: [stats for file2],
 *     //     f3: [stats for file3]
 *     // }
 * });
 */

var mapValues = doLimit(mapValuesLimit, Infinity);

/**
 * The same as [`mapValues`]{@link module:Collections.mapValues} but runs only a single async operation at a time.
 *
 * @name mapValuesSeries
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.mapValues]{@link module:Collections.mapValues}
 * @category Collection
 * @param {Object} obj - A collection to iterate over.
 * @param {Function} iteratee - A function to apply to each value in `obj`.
 * The iteratee is passed a `callback(err, transformed)` which must be called
 * once it has completed with an error (which can be `null`) and a
 * transformed value. Invoked with (value, key, callback).
 * @param {Function} [callback] - A callback which is called when all `iteratee`
 * functions have finished, or an error occurs. `result` is a new object consisting
 * of each key from `obj`, with each transformed value on the right-hand side.
 * Invoked with (err, result).
 */
var mapValuesSeries = doLimit(mapValuesLimit, 1);

function has(obj, key) {
    return key in obj;
}

/**
 * Caches the results of an `async` function. When creating a hash to store
 * function results against, the callback is omitted from the hash and an
 * optional hash function can be used.
 *
 * If no hash function is specified, the first argument is used as a hash key,
 * which may work reasonably if it is a string or a data type that converts to a
 * distinct string. Note that objects and arrays will not behave reasonably.
 * Neither will cases where the other arguments are significant. In such cases,
 * specify your own hash function.
 *
 * The cache of results is exposed as the `memo` property of the function
 * returned by `memoize`.
 *
 * @name memoize
 * @static
 * @memberOf module:Utils
 * @method
 * @category Util
 * @param {Function} fn - The function to proxy and cache results from.
 * @param {Function} hasher - An optional function for generating a custom hash
 * for storing results. It has all the arguments applied to it apart from the
 * callback, and must be synchronous.
 * @returns {Function} a memoized version of `fn`
 * @example
 *
 * var slow_fn = function(name, callback) {
 *     // do something
 *     callback(null, result);
 * };
 * var fn = async.memoize(slow_fn);
 *
 * // fn can now be used as if it were slow_fn
 * fn('some name', function() {
 *     // callback
 * });
 */
function memoize(fn, hasher) {
    var memo = Object.create(null);
    var queues = Object.create(null);
    hasher = hasher || identity;
    var memoized = initialParams(function memoized(args, callback) {
        var key = hasher.apply(null, args);
        if (has(memo, key)) {
            setImmediate$1(function () {
                callback.apply(null, memo[key]);
            });
        } else if (has(queues, key)) {
            queues[key].push(callback);
        } else {
            queues[key] = [callback];
            fn.apply(null, args.concat([rest(function (args) {
                memo[key] = args;
                var q = queues[key];
                delete queues[key];
                for (var i = 0, l = q.length; i < l; i++) {
                    q[i].apply(null, args);
                }
            })]));
        }
    });
    memoized.memo = memo;
    memoized.unmemoized = fn;
    return memoized;
}

/**
 * Calls `callback` on a later loop around the event loop. In Node.js this just
 * calls `setImmediate`.  In the browser it will use `setImmediate` if
 * available, otherwise `setTimeout(callback, 0)`, which means other higher
 * priority events may precede the execution of `callback`.
 *
 * This is used internally for browser-compatibility purposes.
 *
 * @name nextTick
 * @static
 * @memberOf module:Utils
 * @method
 * @alias setImmediate
 * @category Util
 * @param {Function} callback - The function to call on a later loop around
 * the event loop. Invoked with (args...).
 * @param {...*} args... - any number of additional arguments to pass to the
 * callback on the next tick.
 * @example
 *
 * var call_order = [];
 * async.nextTick(function() {
 *     call_order.push('two');
 *     // call_order now equals ['one','two']
 * });
 * call_order.push('one');
 *
 * async.setImmediate(function (a, b, c) {
 *     // a, b, and c equal 1, 2, and 3
 * }, 1, 2, 3);
 */
var _defer$1;

if (hasNextTick) {
    _defer$1 = process.nextTick;
} else if (hasSetImmediate) {
    _defer$1 = setImmediate;
} else {
    _defer$1 = fallback;
}

var nextTick = wrap(_defer$1);

function _parallel(eachfn, tasks, callback) {
    callback = callback || noop;
    var results = isArrayLike(tasks) ? [] : {};

    eachfn(tasks, function (task, key, callback) {
        task(rest(function (err, args) {
            if (args.length <= 1) {
                args = args[0];
            }
            results[key] = args;
            callback(err);
        }));
    }, function (err) {
        callback(err, results);
    });
}

/**
 * Run the `tasks` collection of functions in parallel, without waiting until
 * the previous function has completed. If any of the functions pass an error to
 * its callback, the main `callback` is immediately called with the value of the
 * error. Once the `tasks` have completed, the results are passed to the final
 * `callback` as an array.
 *
 * **Note:** `parallel` is about kicking-off I/O tasks in parallel, not about
 * parallel execution of code.  If your tasks do not use any timers or perform
 * any I/O, they will actually be executed in series.  Any synchronous setup
 * sections for each task will happen one after the other.  JavaScript remains
 * single-threaded.
 *
 * It is also possible to use an object instead of an array. Each property will
 * be run as a function and the results will be passed to the final `callback`
 * as an object instead of an array. This can be a more readable way of handling
 * results from {@link async.parallel}.
 *
 * @name parallel
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @category Control Flow
 * @param {Array|Iterable|Object} tasks - A collection containing functions to run.
 * Each function is passed a `callback(err, result)` which it must call on
 * completion with an error `err` (which can be `null`) and an optional `result`
 * value.
 * @param {Function} [callback] - An optional callback to run once all the
 * functions have completed successfully. This function gets a results array
 * (or object) containing all the result arguments passed to the task callbacks.
 * Invoked with (err, results).
 * @example
 * async.parallel([
 *     function(callback) {
 *         setTimeout(function() {
 *             callback(null, 'one');
 *         }, 200);
 *     },
 *     function(callback) {
 *         setTimeout(function() {
 *             callback(null, 'two');
 *         }, 100);
 *     }
 * ],
 * // optional callback
 * function(err, results) {
 *     // the results array will equal ['one','two'] even though
 *     // the second function had a shorter timeout.
 * });
 *
 * // an example using an object instead of an array
 * async.parallel({
 *     one: function(callback) {
 *         setTimeout(function() {
 *             callback(null, 1);
 *         }, 200);
 *     },
 *     two: function(callback) {
 *         setTimeout(function() {
 *             callback(null, 2);
 *         }, 100);
 *     }
 * }, function(err, results) {
 *     // results is now equals to: {one: 1, two: 2}
 * });
 */
function parallelLimit(tasks, callback) {
  _parallel(eachOf, tasks, callback);
}

/**
 * The same as [`parallel`]{@link module:ControlFlow.parallel} but runs a maximum of `limit` async operations at a
 * time.
 *
 * @name parallelLimit
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.parallel]{@link module:ControlFlow.parallel}
 * @category Control Flow
 * @param {Array|Collection} tasks - A collection containing functions to run.
 * Each function is passed a `callback(err, result)` which it must call on
 * completion with an error `err` (which can be `null`) and an optional `result`
 * value.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {Function} [callback] - An optional callback to run once all the
 * functions have completed successfully. This function gets a results array
 * (or object) containing all the result arguments passed to the task callbacks.
 * Invoked with (err, results).
 */
function parallelLimit$1(tasks, limit, callback) {
  _parallel(_eachOfLimit(limit), tasks, callback);
}

/**
 * A queue of tasks for the worker function to complete.
 * @typedef {Object} QueueObject
 * @memberOf module:ControlFlow
 * @property {Function} length - a function returning the number of items
 * waiting to be processed. Invoke with `queue.length()`.
 * @property {boolean} started - a boolean indicating whether or not any
 * items have been pushed and processed by the queue.
 * @property {Function} running - a function returning the number of items
 * currently being processed. Invoke with `queue.running()`.
 * @property {Function} workersList - a function returning the array of items
 * currently being processed. Invoke with `queue.workersList()`.
 * @property {Function} idle - a function returning false if there are items
 * waiting or being processed, or true if not. Invoke with `queue.idle()`.
 * @property {number} concurrency - an integer for determining how many `worker`
 * functions should be run in parallel. This property can be changed after a
 * `queue` is created to alter the concurrency on-the-fly.
 * @property {Function} push - add a new task to the `queue`. Calls `callback`
 * once the `worker` has finished processing the task. Instead of a single task,
 * a `tasks` array can be submitted. The respective callback is used for every
 * task in the list. Invoke with `queue.push(task, [callback])`,
 * @property {Function} unshift - add a new task to the front of the `queue`.
 * Invoke with `queue.unshift(task, [callback])`.
 * @property {Function} saturated - a callback that is called when the number of
 * running workers hits the `concurrency` limit, and further tasks will be
 * queued.
 * @property {Function} unsaturated - a callback that is called when the number
 * of running workers is less than the `concurrency` & `buffer` limits, and
 * further tasks will not be queued.
 * @property {number} buffer - A minimum threshold buffer in order to say that
 * the `queue` is `unsaturated`.
 * @property {Function} empty - a callback that is called when the last item
 * from the `queue` is given to a `worker`.
 * @property {Function} drain - a callback that is called when the last item
 * from the `queue` has returned from the `worker`.
 * @property {Function} error - a callback that is called when a task errors.
 * Has the signature `function(error, task)`.
 * @property {boolean} paused - a boolean for determining whether the queue is
 * in a paused state.
 * @property {Function} pause - a function that pauses the processing of tasks
 * until `resume()` is called. Invoke with `queue.pause()`.
 * @property {Function} resume - a function that resumes the processing of
 * queued tasks when the queue is paused. Invoke with `queue.resume()`.
 * @property {Function} kill - a function that removes the `drain` callback and
 * empties remaining tasks from the queue forcing it to go idle. Invoke with `queue.kill()`.
 */

/**
 * Creates a `queue` object with the specified `concurrency`. Tasks added to the
 * `queue` are processed in parallel (up to the `concurrency` limit). If all
 * `worker`s are in progress, the task is queued until one becomes available.
 * Once a `worker` completes a `task`, that `task`'s callback is called.
 *
 * @name queue
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @category Control Flow
 * @param {Function} worker - An asynchronous function for processing a queued
 * task, which must call its `callback(err)` argument when finished, with an
 * optional `error` as an argument.  If you want to handle errors from an
 * individual task, pass a callback to `q.push()`. Invoked with
 * (task, callback).
 * @param {number} [concurrency=1] - An `integer` for determining how many
 * `worker` functions should be run in parallel.  If omitted, the concurrency
 * defaults to `1`.  If the concurrency is `0`, an error is thrown.
 * @returns {module:ControlFlow.QueueObject} A queue object to manage the tasks. Callbacks can
 * attached as certain properties to listen for specific events during the
 * lifecycle of the queue.
 * @example
 *
 * // create a queue object with concurrency 2
 * var q = async.queue(function(task, callback) {
 *     console.log('hello ' + task.name);
 *     callback();
 * }, 2);
 *
 * // assign a callback
 * q.drain = function() {
 *     console.log('all items have been processed');
 * };
 *
 * // add some items to the queue
 * q.push({name: 'foo'}, function(err) {
 *     console.log('finished processing foo');
 * });
 * q.push({name: 'bar'}, function (err) {
 *     console.log('finished processing bar');
 * });
 *
 * // add some items to the queue (batch-wise)
 * q.push([{name: 'baz'},{name: 'bay'},{name: 'bax'}], function(err) {
 *     console.log('finished processing item');
 * });
 *
 * // add some items to the front of the queue
 * q.unshift({name: 'bar'}, function (err) {
 *     console.log('finished processing bar');
 * });
 */
var queue$1 = function (worker, concurrency) {
  return queue(function (items, cb) {
    worker(items[0], cb);
  }, concurrency, 1);
};

/**
 * The same as [async.queue]{@link module:ControlFlow.queue} only tasks are assigned a priority and
 * completed in ascending priority order.
 *
 * @name priorityQueue
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.queue]{@link module:ControlFlow.queue}
 * @category Control Flow
 * @param {Function} worker - An asynchronous function for processing a queued
 * task, which must call its `callback(err)` argument when finished, with an
 * optional `error` as an argument.  If you want to handle errors from an
 * individual task, pass a callback to `q.push()`. Invoked with
 * (task, callback).
 * @param {number} concurrency - An `integer` for determining how many `worker`
 * functions should be run in parallel.  If omitted, the concurrency defaults to
 * `1`.  If the concurrency is `0`, an error is thrown.
 * @returns {module:ControlFlow.QueueObject} A priorityQueue object to manage the tasks. There are two
 * differences between `queue` and `priorityQueue` objects:
 * * `push(task, priority, [callback])` - `priority` should be a number. If an
 *   array of `tasks` is given, all tasks will be assigned the same priority.
 * * The `unshift` method was removed.
 */
var priorityQueue = function (worker, concurrency) {
    // Start with a normal queue
    var q = queue$1(worker, concurrency);

    // Override push to accept second parameter representing priority
    q.push = function (data, priority, callback) {
        if (callback == null) callback = noop;
        if (typeof callback !== 'function') {
            throw new Error('task callback must be a function');
        }
        q.started = true;
        if (!isArray(data)) {
            data = [data];
        }
        if (data.length === 0) {
            // call drain immediately if there are no tasks
            return setImmediate$1(function () {
                q.drain();
            });
        }

        priority = priority || 0;
        var nextNode = q._tasks.head;
        while (nextNode && priority >= nextNode.priority) {
            nextNode = nextNode.next;
        }

        for (var i = 0, l = data.length; i < l; i++) {
            var item = {
                data: data[i],
                priority: priority,
                callback: callback
            };

            if (nextNode) {
                q._tasks.insertBefore(nextNode, item);
            } else {
                q._tasks.push(item);
            }
        }
        setImmediate$1(q.process);
    };

    // Remove unshift function
    delete q.unshift;

    return q;
};

/**
 * Runs the `tasks` array of functions in parallel, without waiting until the
 * previous function has completed. Once any of the `tasks` complete or pass an
 * error to its callback, the main `callback` is immediately called. It's
 * equivalent to `Promise.race()`.
 *
 * @name race
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @category Control Flow
 * @param {Array} tasks - An array containing functions to run. Each function
 * is passed a `callback(err, result)` which it must call on completion with an
 * error `err` (which can be `null`) and an optional `result` value.
 * @param {Function} callback - A callback to run once any of the functions have
 * completed. This function gets an error or result from the first function that
 * completed. Invoked with (err, result).
 * @returns undefined
 * @example
 *
 * async.race([
 *     function(callback) {
 *         setTimeout(function() {
 *             callback(null, 'one');
 *         }, 200);
 *     },
 *     function(callback) {
 *         setTimeout(function() {
 *             callback(null, 'two');
 *         }, 100);
 *     }
 * ],
 * // main callback
 * function(err, result) {
 *     // the result will be equal to 'two' as it finishes earlier
 * });
 */
function race(tasks, callback) {
    callback = once(callback || noop);
    if (!isArray(tasks)) return callback(new TypeError('First argument to race must be an array of functions'));
    if (!tasks.length) return callback();
    for (var i = 0, l = tasks.length; i < l; i++) {
        tasks[i](callback);
    }
}

var slice = Array.prototype.slice;

/**
 * Same as [`reduce`]{@link module:Collections.reduce}, only operates on `array` in reverse order.
 *
 * @name reduceRight
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.reduce]{@link module:Collections.reduce}
 * @alias foldr
 * @category Collection
 * @param {Array} array - A collection to iterate over.
 * @param {*} memo - The initial state of the reduction.
 * @param {Function} iteratee - A function applied to each item in the
 * array to produce the next step in the reduction. The `iteratee` is passed a
 * `callback(err, reduction)` which accepts an optional error as its first
 * argument, and the state of the reduction as the second. If an error is
 * passed to the callback, the reduction is stopped and the main `callback` is
 * immediately called with the error. Invoked with (memo, item, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Result is the reduced value. Invoked with
 * (err, result).
 */
function reduceRight(array, memo, iteratee, callback) {
  var reversed = slice.call(array).reverse();
  reduce(reversed, memo, iteratee, callback);
}

/**
 * Wraps the function in another function that always returns data even when it
 * errors.
 *
 * The object returned has either the property `error` or `value`.
 *
 * @name reflect
 * @static
 * @memberOf module:Utils
 * @method
 * @category Util
 * @param {Function} fn - The function you want to wrap
 * @returns {Function} - A function that always passes null to it's callback as
 * the error. The second argument to the callback will be an `object` with
 * either an `error` or a `value` property.
 * @example
 *
 * async.parallel([
 *     async.reflect(function(callback) {
 *         // do some stuff ...
 *         callback(null, 'one');
 *     }),
 *     async.reflect(function(callback) {
 *         // do some more stuff but error ...
 *         callback('bad stuff happened');
 *     }),
 *     async.reflect(function(callback) {
 *         // do some more stuff ...
 *         callback(null, 'two');
 *     })
 * ],
 * // optional callback
 * function(err, results) {
 *     // values
 *     // results[0].value = 'one'
 *     // results[1].error = 'bad stuff happened'
 *     // results[2].value = 'two'
 * });
 */
function reflect(fn) {
    return initialParams(function reflectOn(args, reflectCallback) {
        args.push(rest(function callback(err, cbArgs) {
            if (err) {
                reflectCallback(null, {
                    error: err
                });
            } else {
                var value = null;
                if (cbArgs.length === 1) {
                    value = cbArgs[0];
                } else if (cbArgs.length > 1) {
                    value = cbArgs;
                }
                reflectCallback(null, {
                    value: value
                });
            }
        }));

        return fn.apply(this, args);
    });
}

function reject$1(eachfn, arr, iteratee, callback) {
    _filter(eachfn, arr, function (value, cb) {
        iteratee(value, function (err, v) {
            cb(err, !v);
        });
    }, callback);
}

/**
 * The opposite of [`filter`]{@link module:Collections.filter}. Removes values that pass an `async` truth test.
 *
 * @name reject
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.filter]{@link module:Collections.filter}
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {Function} iteratee - A truth test to apply to each item in `coll`.
 * The `iteratee` is passed a `callback(err, truthValue)`, which must be called
 * with a boolean argument once it has completed. Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Invoked with (err, results).
 * @example
 *
 * async.reject(['file1','file2','file3'], function(filePath, callback) {
 *     fs.access(filePath, function(err) {
 *         callback(null, !err)
 *     });
 * }, function(err, results) {
 *     // results now equals an array of missing files
 *     createFiles(results);
 * });
 */
var reject = doParallel(reject$1);

/**
 * A helper function that wraps an array or an object of functions with reflect.
 *
 * @name reflectAll
 * @static
 * @memberOf module:Utils
 * @method
 * @see [async.reflect]{@link module:Utils.reflect}
 * @category Util
 * @param {Array} tasks - The array of functions to wrap in `async.reflect`.
 * @returns {Array} Returns an array of functions, each function wrapped in
 * `async.reflect`
 * @example
 *
 * let tasks = [
 *     function(callback) {
 *         setTimeout(function() {
 *             callback(null, 'one');
 *         }, 200);
 *     },
 *     function(callback) {
 *         // do some more stuff but error ...
 *         callback(new Error('bad stuff happened'));
 *     },
 *     function(callback) {
 *         setTimeout(function() {
 *             callback(null, 'two');
 *         }, 100);
 *     }
 * ];
 *
 * async.parallel(async.reflectAll(tasks),
 * // optional callback
 * function(err, results) {
 *     // values
 *     // results[0].value = 'one'
 *     // results[1].error = Error('bad stuff happened')
 *     // results[2].value = 'two'
 * });
 *
 * // an example using an object instead of an array
 * let tasks = {
 *     one: function(callback) {
 *         setTimeout(function() {
 *             callback(null, 'one');
 *         }, 200);
 *     },
 *     two: function(callback) {
 *         callback('two');
 *     },
 *     three: function(callback) {
 *         setTimeout(function() {
 *             callback(null, 'three');
 *         }, 100);
 *     }
 * };
 *
 * async.parallel(async.reflectAll(tasks),
 * // optional callback
 * function(err, results) {
 *     // values
 *     // results.one.value = 'one'
 *     // results.two.error = 'two'
 *     // results.three.value = 'three'
 * });
 */
function reflectAll(tasks) {
    var results;
    if (isArray(tasks)) {
        results = arrayMap(tasks, reflect);
    } else {
        results = {};
        baseForOwn(tasks, function (task, key) {
            results[key] = reflect.call(this, task);
        });
    }
    return results;
}

/**
 * The same as [`reject`]{@link module:Collections.reject} but runs a maximum of `limit` async operations at a
 * time.
 *
 * @name rejectLimit
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.reject]{@link module:Collections.reject}
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {Function} iteratee - A truth test to apply to each item in `coll`.
 * The `iteratee` is passed a `callback(err, truthValue)`, which must be called
 * with a boolean argument once it has completed. Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Invoked with (err, results).
 */
var rejectLimit = doParallelLimit(reject$1);

/**
 * The same as [`reject`]{@link module:Collections.reject} but runs only a single async operation at a time.
 *
 * @name rejectSeries
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.reject]{@link module:Collections.reject}
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {Function} iteratee - A truth test to apply to each item in `coll`.
 * The `iteratee` is passed a `callback(err, truthValue)`, which must be called
 * with a boolean argument once it has completed. Invoked with (item, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Invoked with (err, results).
 */
var rejectSeries = doLimit(rejectLimit, 1);

/**
 * Creates a function that returns `value`.
 *
 * @static
 * @memberOf _
 * @since 2.4.0
 * @category Util
 * @param {*} value The value to return from the new function.
 * @returns {Function} Returns the new constant function.
 * @example
 *
 * var objects = _.times(2, _.constant({ 'a': 1 }));
 *
 * console.log(objects);
 * // => [{ 'a': 1 }, { 'a': 1 }]
 *
 * console.log(objects[0] === objects[1]);
 * // => true
 */
function constant$1(value) {
  return function() {
    return value;
  };
}

/**
 * Attempts to get a successful response from `task` no more than `times` times
 * before returning an error. If the task is successful, the `callback` will be
 * passed the result of the successful task. If all attempts fail, the callback
 * will be passed the error and result (if any) of the final attempt.
 *
 * @name retry
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @category Control Flow
 * @param {Object|number} [opts = {times: 5, interval: 0}| 5] - Can be either an
 * object with `times` and `interval` or a number.
 * * `times` - The number of attempts to make before giving up.  The default
 *   is `5`.
 * * `interval` - The time to wait between retries, in milliseconds.  The
 *   default is `0`. The interval may also be specified as a function of the
 *   retry count (see example).
 * * `errorFilter` - An optional synchronous function that is invoked on
 *   erroneous result. If it returns `true` the retry attempts will continue;
 *   if the function returns `false` the retry flow is aborted with the current
 *   attempt's error and result being returned to the final callback.
 *   Invoked with (err).
 * * If `opts` is a number, the number specifies the number of times to retry,
 *   with the default interval of `0`.
 * @param {Function} task - A function which receives two arguments: (1) a
 * `callback(err, result)` which must be called when finished, passing `err`
 * (which can be `null`) and the `result` of the function's execution, and (2)
 * a `results` object, containing the results of the previously executed
 * functions (if nested inside another control flow). Invoked with
 * (callback, results).
 * @param {Function} [callback] - An optional callback which is called when the
 * task has succeeded, or after the final failed attempt. It receives the `err`
 * and `result` arguments of the last attempt at completing the `task`. Invoked
 * with (err, results).
 * @example
 *
 * // The `retry` function can be used as a stand-alone control flow by passing
 * // a callback, as shown below:
 *
 * // try calling apiMethod 3 times
 * async.retry(3, apiMethod, function(err, result) {
 *     // do something with the result
 * });
 *
 * // try calling apiMethod 3 times, waiting 200 ms between each retry
 * async.retry({times: 3, interval: 200}, apiMethod, function(err, result) {
 *     // do something with the result
 * });
 *
 * // try calling apiMethod 10 times with exponential backoff
 * // (i.e. intervals of 100, 200, 400, 800, 1600, ... milliseconds)
 * async.retry({
 *   times: 10,
 *   interval: function(retryCount) {
 *     return 50 * Math.pow(2, retryCount);
 *   }
 * }, apiMethod, function(err, result) {
 *     // do something with the result
 * });
 *
 * // try calling apiMethod the default 5 times no delay between each retry
 * async.retry(apiMethod, function(err, result) {
 *     // do something with the result
 * });
 *
 * // try calling apiMethod only when error condition satisfies, all other
 * // errors will abort the retry control flow and return to final callback
 * async.retry({
 *   errorFilter: function(err) {
 *     return err.message === 'Temporary error'; // only retry on a specific error
 *   }
 * }, apiMethod, function(err, result) {
 *     // do something with the result
 * });
 *
 * // It can also be embedded within other control flow functions to retry
 * // individual methods that are not as reliable, like this:
 * async.auto({
 *     users: api.getUsers.bind(api),
 *     payments: async.retry(3, api.getPayments.bind(api))
 * }, function(err, results) {
 *     // do something with the results
 * });
 *
 */
function retry(opts, task, callback) {
    var DEFAULT_TIMES = 5;
    var DEFAULT_INTERVAL = 0;

    var options = {
        times: DEFAULT_TIMES,
        intervalFunc: constant$1(DEFAULT_INTERVAL)
    };

    function parseTimes(acc, t) {
        if (typeof t === 'object') {
            acc.times = +t.times || DEFAULT_TIMES;

            acc.intervalFunc = typeof t.interval === 'function' ? t.interval : constant$1(+t.interval || DEFAULT_INTERVAL);

            acc.errorFilter = t.errorFilter;
        } else if (typeof t === 'number' || typeof t === 'string') {
            acc.times = +t || DEFAULT_TIMES;
        } else {
            throw new Error("Invalid arguments for async.retry");
        }
    }

    if (arguments.length < 3 && typeof opts === 'function') {
        callback = task || noop;
        task = opts;
    } else {
        parseTimes(options, opts);
        callback = callback || noop;
    }

    if (typeof task !== 'function') {
        throw new Error("Invalid arguments for async.retry");
    }

    var attempt = 1;
    function retryAttempt() {
        task(function (err) {
            if (err && attempt++ < options.times && (typeof options.errorFilter != 'function' || options.errorFilter(err))) {
                setTimeout(retryAttempt, options.intervalFunc(attempt));
            } else {
                callback.apply(null, arguments);
            }
        });
    }

    retryAttempt();
}

/**
 * A close relative of [`retry`]{@link module:ControlFlow.retry}.  This method wraps a task and makes it
 * retryable, rather than immediately calling it with retries.
 *
 * @name retryable
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.retry]{@link module:ControlFlow.retry}
 * @category Control Flow
 * @param {Object|number} [opts = {times: 5, interval: 0}| 5] - optional
 * options, exactly the same as from `retry`
 * @param {Function} task - the asynchronous function to wrap
 * @returns {Functions} The wrapped function, which when invoked, will retry on
 * an error, based on the parameters specified in `opts`.
 * @example
 *
 * async.auto({
 *     dep1: async.retryable(3, getFromFlakyService),
 *     process: ["dep1", async.retryable(3, function (results, cb) {
 *         maybeProcessData(results.dep1, cb);
 *     })]
 * }, callback);
 */
var retryable = function (opts, task) {
    if (!task) {
        task = opts;
        opts = null;
    }
    return initialParams(function (args, callback) {
        function taskFn(cb) {
            task.apply(null, args.concat([cb]));
        }

        if (opts) retry(opts, taskFn, callback);else retry(taskFn, callback);
    });
};

/**
 * Run the functions in the `tasks` collection in series, each one running once
 * the previous function has completed. If any functions in the series pass an
 * error to its callback, no more functions are run, and `callback` is
 * immediately called with the value of the error. Otherwise, `callback`
 * receives an array of results when `tasks` have completed.
 *
 * It is also possible to use an object instead of an array. Each property will
 * be run as a function, and the results will be passed to the final `callback`
 * as an object instead of an array. This can be a more readable way of handling
 *  results from {@link async.series}.
 *
 * **Note** that while many implementations preserve the order of object
 * properties, the [ECMAScript Language Specification](http://www.ecma-international.org/ecma-262/5.1/#sec-8.6)
 * explicitly states that
 *
 * > The mechanics and order of enumerating the properties is not specified.
 *
 * So if you rely on the order in which your series of functions are executed,
 * and want this to work on all platforms, consider using an array.
 *
 * @name series
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @category Control Flow
 * @param {Array|Iterable|Object} tasks - A collection containing functions to run, each
 * function is passed a `callback(err, result)` it must call on completion with
 * an error `err` (which can be `null`) and an optional `result` value.
 * @param {Function} [callback] - An optional callback to run once all the
 * functions have completed. This function gets a results array (or object)
 * containing all the result arguments passed to the `task` callbacks. Invoked
 * with (err, result).
 * @example
 * async.series([
 *     function(callback) {
 *         // do some stuff ...
 *         callback(null, 'one');
 *     },
 *     function(callback) {
 *         // do some more stuff ...
 *         callback(null, 'two');
 *     }
 * ],
 * // optional callback
 * function(err, results) {
 *     // results is now equal to ['one', 'two']
 * });
 *
 * async.series({
 *     one: function(callback) {
 *         setTimeout(function() {
 *             callback(null, 1);
 *         }, 200);
 *     },
 *     two: function(callback){
 *         setTimeout(function() {
 *             callback(null, 2);
 *         }, 100);
 *     }
 * }, function(err, results) {
 *     // results is now equal to: {one: 1, two: 2}
 * });
 */
function series(tasks, callback) {
  _parallel(eachOfSeries, tasks, callback);
}

/**
 * Returns `true` if at least one element in the `coll` satisfies an async test.
 * If any iteratee call returns `true`, the main `callback` is immediately
 * called.
 *
 * @name some
 * @static
 * @memberOf module:Collections
 * @method
 * @alias any
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {Function} iteratee - A truth test to apply to each item in the array
 * in parallel. The iteratee is passed a `callback(err, truthValue)` which must
 * be called with a boolean argument once it has completed. Invoked with
 * (item, callback).
 * @param {Function} [callback] - A callback which is called as soon as any
 * iteratee returns `true`, or after all the iteratee functions have finished.
 * Result will be either `true` or `false` depending on the values of the async
 * tests. Invoked with (err, result).
 * @example
 *
 * async.some(['file1','file2','file3'], function(filePath, callback) {
 *     fs.access(filePath, function(err) {
 *         callback(null, !err)
 *     });
 * }, function(err, result) {
 *     // if result is true then at least one of the files exists
 * });
 */
var some = _createTester(eachOf, Boolean, identity);

/**
 * The same as [`some`]{@link module:Collections.some} but runs a maximum of `limit` async operations at a time.
 *
 * @name someLimit
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.some]{@link module:Collections.some}
 * @alias anyLimit
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {Function} iteratee - A truth test to apply to each item in the array
 * in parallel. The iteratee is passed a `callback(err, truthValue)` which must
 * be called with a boolean argument once it has completed. Invoked with
 * (item, callback).
 * @param {Function} [callback] - A callback which is called as soon as any
 * iteratee returns `true`, or after all the iteratee functions have finished.
 * Result will be either `true` or `false` depending on the values of the async
 * tests. Invoked with (err, result).
 */
var someLimit = _createTester(eachOfLimit, Boolean, identity);

/**
 * The same as [`some`]{@link module:Collections.some} but runs only a single async operation at a time.
 *
 * @name someSeries
 * @static
 * @memberOf module:Collections
 * @method
 * @see [async.some]{@link module:Collections.some}
 * @alias anySeries
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {Function} iteratee - A truth test to apply to each item in the array
 * in parallel. The iteratee is passed a `callback(err, truthValue)` which must
 * be called with a boolean argument once it has completed. Invoked with
 * (item, callback).
 * @param {Function} [callback] - A callback which is called as soon as any
 * iteratee returns `true`, or after all the iteratee functions have finished.
 * Result will be either `true` or `false` depending on the values of the async
 * tests. Invoked with (err, result).
 */
var someSeries = doLimit(someLimit, 1);

/**
 * Sorts a list by the results of running each `coll` value through an async
 * `iteratee`.
 *
 * @name sortBy
 * @static
 * @memberOf module:Collections
 * @method
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {Function} iteratee - A function to apply to each item in `coll`.
 * The iteratee is passed a `callback(err, sortValue)` which must be called once
 * it has completed with an error (which can be `null`) and a value to use as
 * the sort criteria. Invoked with (item, callback).
 * @param {Function} callback - A callback which is called after all the
 * `iteratee` functions have finished, or an error occurs. Results is the items
 * from the original `coll` sorted by the values returned by the `iteratee`
 * calls. Invoked with (err, results).
 * @example
 *
 * async.sortBy(['file1','file2','file3'], function(file, callback) {
 *     fs.stat(file, function(err, stats) {
 *         callback(err, stats.mtime);
 *     });
 * }, function(err, results) {
 *     // results is now the original array of files sorted by
 *     // modified date
 * });
 *
 * // By modifying the callback parameter the
 * // sorting order can be influenced:
 *
 * // ascending order
 * async.sortBy([1,9,3,5], function(x, callback) {
 *     callback(null, x);
 * }, function(err,result) {
 *     // result callback
 * });
 *
 * // descending order
 * async.sortBy([1,9,3,5], function(x, callback) {
 *     callback(null, x*-1);    //<- x*-1 instead of x, turns the order around
 * }, function(err,result) {
 *     // result callback
 * });
 */
function sortBy(coll, iteratee, callback) {
    map(coll, function (x, callback) {
        iteratee(x, function (err, criteria) {
            if (err) return callback(err);
            callback(null, { value: x, criteria: criteria });
        });
    }, function (err, results) {
        if (err) return callback(err);
        callback(null, arrayMap(results.sort(comparator), baseProperty('value')));
    });

    function comparator(left, right) {
        var a = left.criteria,
            b = right.criteria;
        return a < b ? -1 : a > b ? 1 : 0;
    }
}

/**
 * Sets a time limit on an asynchronous function. If the function does not call
 * its callback within the specified milliseconds, it will be called with a
 * timeout error. The code property for the error object will be `'ETIMEDOUT'`.
 *
 * @name timeout
 * @static
 * @memberOf module:Utils
 * @method
 * @category Util
 * @param {Function} asyncFn - The asynchronous function you want to set the
 * time limit.
 * @param {number} milliseconds - The specified time limit.
 * @param {*} [info] - Any variable you want attached (`string`, `object`, etc)
 * to timeout Error for more information..
 * @returns {Function} Returns a wrapped function that can be used with any of
 * the control flow functions. Invoke this function with the same
 * parameters as you would `asyncFunc`.
 * @example
 *
 * function myFunction(foo, callback) {
 *     doAsyncTask(foo, function(err, data) {
 *         // handle errors
 *         if (err) return callback(err);
 *
 *         // do some stuff ...
 *
 *         // return processed data
 *         return callback(null, data);
 *     });
 * }
 *
 * var wrapped = async.timeout(myFunction, 1000);
 *
 * // call `wrapped` as you would `myFunction`
 * wrapped({ bar: 'bar' }, function(err, data) {
 *     // if `myFunction` takes < 1000 ms to execute, `err`
 *     // and `data` will have their expected values
 *
 *     // else `err` will be an Error with the code 'ETIMEDOUT'
 * });
 */
function timeout(asyncFn, milliseconds, info) {
    var originalCallback, timer;
    var timedOut = false;

    function injectedCallback() {
        if (!timedOut) {
            originalCallback.apply(null, arguments);
            clearTimeout(timer);
        }
    }

    function timeoutCallback() {
        var name = asyncFn.name || 'anonymous';
        var error = new Error('Callback function "' + name + '" timed out.');
        error.code = 'ETIMEDOUT';
        if (info) {
            error.info = info;
        }
        timedOut = true;
        originalCallback(error);
    }

    return initialParams(function (args, origCallback) {
        originalCallback = origCallback;
        // setup timer and call original function
        timer = setTimeout(timeoutCallback, milliseconds);
        asyncFn.apply(null, args.concat(injectedCallback));
    });
}

/* Built-in method references for those with the same name as other `lodash` methods. */
var nativeCeil = Math.ceil;
var nativeMax$1 = Math.max;

/**
 * The base implementation of `_.range` and `_.rangeRight` which doesn't
 * coerce arguments.
 *
 * @private
 * @param {number} start The start of the range.
 * @param {number} end The end of the range.
 * @param {number} step The value to increment or decrement by.
 * @param {boolean} [fromRight] Specify iterating from right to left.
 * @returns {Array} Returns the range of numbers.
 */
function baseRange(start, end, step, fromRight) {
  var index = -1,
      length = nativeMax$1(nativeCeil((end - start) / (step || 1)), 0),
      result = Array(length);

  while (length--) {
    result[fromRight ? length : ++index] = start;
    start += step;
  }
  return result;
}

/**
 * The same as [times]{@link module:ControlFlow.times} but runs a maximum of `limit` async operations at a
 * time.
 *
 * @name timesLimit
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.times]{@link module:ControlFlow.times}
 * @category Control Flow
 * @param {number} count - The number of times to run the function.
 * @param {number} limit - The maximum number of async operations at a time.
 * @param {Function} iteratee - The function to call `n` times. Invoked with the
 * iteration index and a callback (n, next).
 * @param {Function} callback - see [async.map]{@link module:Collections.map}.
 */
function timeLimit(count, limit, iteratee, callback) {
  mapLimit(baseRange(0, count, 1), limit, iteratee, callback);
}

/**
 * Calls the `iteratee` function `n` times, and accumulates results in the same
 * manner you would use with [map]{@link module:Collections.map}.
 *
 * @name times
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.map]{@link module:Collections.map}
 * @category Control Flow
 * @param {number} n - The number of times to run the function.
 * @param {Function} iteratee - The function to call `n` times. Invoked with the
 * iteration index and a callback (n, next).
 * @param {Function} callback - see {@link module:Collections.map}.
 * @example
 *
 * // Pretend this is some complicated async factory
 * var createUser = function(id, callback) {
 *     callback(null, {
 *         id: 'user' + id
 *     });
 * };
 *
 * // generate 5 users
 * async.times(5, function(n, next) {
 *     createUser(n, function(err, user) {
 *         next(err, user);
 *     });
 * }, function(err, users) {
 *     // we should now have 5 users
 * });
 */
var times = doLimit(timeLimit, Infinity);

/**
 * The same as [times]{@link module:ControlFlow.times} but runs only a single async operation at a time.
 *
 * @name timesSeries
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.times]{@link module:ControlFlow.times}
 * @category Control Flow
 * @param {number} n - The number of times to run the function.
 * @param {Function} iteratee - The function to call `n` times. Invoked with the
 * iteration index and a callback (n, next).
 * @param {Function} callback - see {@link module:Collections.map}.
 */
var timesSeries = doLimit(timeLimit, 1);

/**
 * A relative of `reduce`.  Takes an Object or Array, and iterates over each
 * element in series, each step potentially mutating an `accumulator` value.
 * The type of the accumulator defaults to the type of collection passed in.
 *
 * @name transform
 * @static
 * @memberOf module:Collections
 * @method
 * @category Collection
 * @param {Array|Iterable|Object} coll - A collection to iterate over.
 * @param {*} [accumulator] - The initial state of the transform.  If omitted,
 * it will default to an empty Object or Array, depending on the type of `coll`
 * @param {Function} iteratee - A function applied to each item in the
 * collection that potentially modifies the accumulator. The `iteratee` is
 * passed a `callback(err)` which accepts an optional error as its first
 * argument. If an error is passed to the callback, the transform is stopped
 * and the main `callback` is immediately called with the error.
 * Invoked with (accumulator, item, key, callback).
 * @param {Function} [callback] - A callback which is called after all the
 * `iteratee` functions have finished. Result is the transformed accumulator.
 * Invoked with (err, result).
 * @example
 *
 * async.transform([1,2,3], function(acc, item, index, callback) {
 *     // pointless async:
 *     process.nextTick(function() {
 *         acc.push(item * 2)
 *         callback(null)
 *     });
 * }, function(err, result) {
 *     // result is now equal to [2, 4, 6]
 * });
 *
 * @example
 *
 * async.transform({a: 1, b: 2, c: 3}, function (obj, val, key, callback) {
 *     setImmediate(function () {
 *         obj[key] = val * 2;
 *         callback();
 *     })
 * }, function (err, result) {
 *     // result is equal to {a: 2, b: 4, c: 6}
 * })
 */
function transform(coll, accumulator, iteratee, callback) {
    if (arguments.length === 3) {
        callback = iteratee;
        iteratee = accumulator;
        accumulator = isArray(coll) ? [] : {};
    }
    callback = once(callback || noop);

    eachOf(coll, function (v, k, cb) {
        iteratee(accumulator, v, k, cb);
    }, function (err) {
        callback(err, accumulator);
    });
}

/**
 * Undoes a [memoize]{@link module:Utils.memoize}d function, reverting it to the original,
 * unmemoized form. Handy for testing.
 *
 * @name unmemoize
 * @static
 * @memberOf module:Utils
 * @method
 * @see [async.memoize]{@link module:Utils.memoize}
 * @category Util
 * @param {Function} fn - the memoized function
 * @returns {Function} a function that calls the original unmemoized function
 */
function unmemoize(fn) {
    return function () {
        return (fn.unmemoized || fn).apply(null, arguments);
    };
}

/**
 * Repeatedly call `iteratee`, while `test` returns `true`. Calls `callback` when
 * stopped, or an error occurs.
 *
 * @name whilst
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @category Control Flow
 * @param {Function} test - synchronous truth test to perform before each
 * execution of `iteratee`. Invoked with ().
 * @param {Function} iteratee - A function which is called each time `test` passes.
 * The function is passed a `callback(err)`, which must be called once it has
 * completed with an optional `err` argument. Invoked with (callback).
 * @param {Function} [callback] - A callback which is called after the test
 * function has failed and repeated execution of `iteratee` has stopped. `callback`
 * will be passed an error and any arguments passed to the final `iteratee`'s
 * callback. Invoked with (err, [results]);
 * @returns undefined
 * @example
 *
 * var count = 0;
 * async.whilst(
 *     function() { return count < 5; },
 *     function(callback) {
 *         count++;
 *         setTimeout(function() {
 *             callback(null, count);
 *         }, 1000);
 *     },
 *     function (err, n) {
 *         // 5 seconds have passed, n = 5
 *     }
 * );
 */
function whilst(test, iteratee, callback) {
    callback = onlyOnce(callback || noop);
    if (!test()) return callback(null);
    var next = rest(function (err, args) {
        if (err) return callback(err);
        if (test()) return iteratee(next);
        callback.apply(null, [null].concat(args));
    });
    iteratee(next);
}

/**
 * Repeatedly call `fn` until `test` returns `true`. Calls `callback` when
 * stopped, or an error occurs. `callback` will be passed an error and any
 * arguments passed to the final `fn`'s callback.
 *
 * The inverse of [whilst]{@link module:ControlFlow.whilst}.
 *
 * @name until
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @see [async.whilst]{@link module:ControlFlow.whilst}
 * @category Control Flow
 * @param {Function} test - synchronous truth test to perform before each
 * execution of `fn`. Invoked with ().
 * @param {Function} fn - A function which is called each time `test` fails.
 * The function is passed a `callback(err)`, which must be called once it has
 * completed with an optional `err` argument. Invoked with (callback).
 * @param {Function} [callback] - A callback which is called after the test
 * function has passed and repeated execution of `fn` has stopped. `callback`
 * will be passed an error and any arguments passed to the final `fn`'s
 * callback. Invoked with (err, [results]);
 */
function until(test, fn, callback) {
    whilst(function () {
        return !test.apply(this, arguments);
    }, fn, callback);
}

/**
 * Runs the `tasks` array of functions in series, each passing their results to
 * the next in the array. However, if any of the `tasks` pass an error to their
 * own callback, the next function is not executed, and the main `callback` is
 * immediately called with the error.
 *
 * @name waterfall
 * @static
 * @memberOf module:ControlFlow
 * @method
 * @category Control Flow
 * @param {Array} tasks - An array of functions to run, each function is passed
 * a `callback(err, result1, result2, ...)` it must call on completion. The
 * first argument is an error (which can be `null`) and any further arguments
 * will be passed as arguments in order to the next task.
 * @param {Function} [callback] - An optional callback to run once all the
 * functions have completed. This will be passed the results of the last task's
 * callback. Invoked with (err, [results]).
 * @returns undefined
 * @example
 *
 * async.waterfall([
 *     function(callback) {
 *         callback(null, 'one', 'two');
 *     },
 *     function(arg1, arg2, callback) {
 *         // arg1 now equals 'one' and arg2 now equals 'two'
 *         callback(null, 'three');
 *     },
 *     function(arg1, callback) {
 *         // arg1 now equals 'three'
 *         callback(null, 'done');
 *     }
 * ], function (err, result) {
 *     // result now equals 'done'
 * });
 *
 * // Or, with named functions:
 * async.waterfall([
 *     myFirstFunction,
 *     mySecondFunction,
 *     myLastFunction,
 * ], function (err, result) {
 *     // result now equals 'done'
 * });
 * function myFirstFunction(callback) {
 *     callback(null, 'one', 'two');
 * }
 * function mySecondFunction(arg1, arg2, callback) {
 *     // arg1 now equals 'one' and arg2 now equals 'two'
 *     callback(null, 'three');
 * }
 * function myLastFunction(arg1, callback) {
 *     // arg1 now equals 'three'
 *     callback(null, 'done');
 * }
 */
var waterfall = function (tasks, callback) {
    callback = once(callback || noop);
    if (!isArray(tasks)) return callback(new Error('First argument to waterfall must be an array of functions'));
    if (!tasks.length) return callback();
    var taskIndex = 0;

    function nextTask(args) {
        if (taskIndex === tasks.length) {
            return callback.apply(null, [null].concat(args));
        }

        var taskCallback = onlyOnce(rest(function (err, args) {
            if (err) {
                return callback.apply(null, [err].concat(args));
            }
            nextTask(args);
        }));

        args.push(taskCallback);

        var task = tasks[taskIndex++];
        task.apply(null, args);
    }

    nextTask([]);
};

/**
 * Async is a utility module which provides straight-forward, powerful functions
 * for working with asynchronous JavaScript. Although originally designed for
 * use with [Node.js](http://nodejs.org) and installable via
 * `npm install --save async`, it can also be used directly in the browser.
 * @module async
 */

/**
 * A collection of `async` functions for manipulating collections, such as
 * arrays and objects.
 * @module Collections
 */

/**
 * A collection of `async` functions for controlling the flow through a script.
 * @module ControlFlow
 */

/**
 * A collection of `async` utility functions.
 * @module Utils
 */
var index = {
  applyEach: applyEach,
  applyEachSeries: applyEachSeries,
  apply: apply$2,
  asyncify: asyncify,
  auto: auto,
  autoInject: autoInject,
  cargo: cargo,
  compose: compose,
  concat: concat,
  concatSeries: concatSeries,
  constant: constant,
  detect: detect,
  detectLimit: detectLimit,
  detectSeries: detectSeries,
  dir: dir,
  doDuring: doDuring,
  doUntil: doUntil,
  doWhilst: doWhilst,
  during: during,
  each: eachLimit,
  eachLimit: eachLimit$1,
  eachOf: eachOf,
  eachOfLimit: eachOfLimit,
  eachOfSeries: eachOfSeries,
  eachSeries: eachSeries,
  ensureAsync: ensureAsync,
  every: every,
  everyLimit: everyLimit,
  everySeries: everySeries,
  filter: filter,
  filterLimit: filterLimit,
  filterSeries: filterSeries,
  forever: forever,
  log: log,
  map: map,
  mapLimit: mapLimit,
  mapSeries: mapSeries,
  mapValues: mapValues,
  mapValuesLimit: mapValuesLimit,
  mapValuesSeries: mapValuesSeries,
  memoize: memoize,
  nextTick: nextTick,
  parallel: parallelLimit,
  parallelLimit: parallelLimit$1,
  priorityQueue: priorityQueue,
  queue: queue$1,
  race: race,
  reduce: reduce,
  reduceRight: reduceRight,
  reflect: reflect,
  reflectAll: reflectAll,
  reject: reject,
  rejectLimit: rejectLimit,
  rejectSeries: rejectSeries,
  retry: retry,
  retryable: retryable,
  seq: seq$1,
  series: series,
  setImmediate: setImmediate$1,
  some: some,
  someLimit: someLimit,
  someSeries: someSeries,
  sortBy: sortBy,
  timeout: timeout,
  times: times,
  timesLimit: timeLimit,
  timesSeries: timesSeries,
  transform: transform,
  unmemoize: unmemoize,
  until: until,
  waterfall: waterfall,
  whilst: whilst,

  // aliases
  all: every,
  any: some,
  forEach: eachLimit,
  forEachSeries: eachSeries,
  forEachLimit: eachLimit$1,
  forEachOf: eachOf,
  forEachOfSeries: eachOfSeries,
  forEachOfLimit: eachOfLimit,
  inject: reduce,
  foldl: reduce,
  foldr: reduceRight,
  select: filter,
  selectLimit: filterLimit,
  selectSeries: filterSeries,
  wrapSync: asyncify
};

exports['default'] = index;
exports.applyEach = applyEach;
exports.applyEachSeries = applyEachSeries;
exports.apply = apply$2;
exports.asyncify = asyncify;
exports.auto = auto;
exports.autoInject = autoInject;
exports.cargo = cargo;
exports.compose = compose;
exports.concat = concat;
exports.concatSeries = concatSeries;
exports.constant = constant;
exports.detect = detect;
exports.detectLimit = detectLimit;
exports.detectSeries = detectSeries;
exports.dir = dir;
exports.doDuring = doDuring;
exports.doUntil = doUntil;
exports.doWhilst = doWhilst;
exports.during = during;
exports.each = eachLimit;
exports.eachLimit = eachLimit$1;
exports.eachOf = eachOf;
exports.eachOfLimit = eachOfLimit;
exports.eachOfSeries = eachOfSeries;
exports.eachSeries = eachSeries;
exports.ensureAsync = ensureAsync;
exports.every = every;
exports.everyLimit = everyLimit;
exports.everySeries = everySeries;
exports.filter = filter;
exports.filterLimit = filterLimit;
exports.filterSeries = filterSeries;
exports.forever = forever;
exports.log = log;
exports.map = map;
exports.mapLimit = mapLimit;
exports.mapSeries = mapSeries;
exports.mapValues = mapValues;
exports.mapValuesLimit = mapValuesLimit;
exports.mapValuesSeries = mapValuesSeries;
exports.memoize = memoize;
exports.nextTick = nextTick;
exports.parallel = parallelLimit;
exports.parallelLimit = parallelLimit$1;
exports.priorityQueue = priorityQueue;
exports.queue = queue$1;
exports.race = race;
exports.reduce = reduce;
exports.reduceRight = reduceRight;
exports.reflect = reflect;
exports.reflectAll = reflectAll;
exports.reject = reject;
exports.rejectLimit = rejectLimit;
exports.rejectSeries = rejectSeries;
exports.retry = retry;
exports.retryable = retryable;
exports.seq = seq$1;
exports.series = series;
exports.setImmediate = setImmediate$1;
exports.some = some;
exports.someLimit = someLimit;
exports.someSeries = someSeries;
exports.sortBy = sortBy;
exports.timeout = timeout;
exports.times = times;
exports.timesLimit = timeLimit;
exports.timesSeries = timesSeries;
exports.transform = transform;
exports.unmemoize = unmemoize;
exports.until = until;
exports.waterfall = waterfall;
exports.whilst = whilst;
exports.all = every;
exports.allLimit = everyLimit;
exports.allSeries = everySeries;
exports.any = some;
exports.anyLimit = someLimit;
exports.anySeries = someSeries;
exports.find = detect;
exports.findLimit = detectLimit;
exports.findSeries = detectSeries;
exports.forEach = eachLimit;
exports.forEachSeries = eachSeries;
exports.forEachLimit = eachLimit$1;
exports.forEachOf = eachOf;
exports.forEachOfSeries = eachOfSeries;
exports.forEachOfLimit = eachOfLimit;
exports.inject = reduce;
exports.foldl = reduce;
exports.foldr = reduceRight;
exports.select = filter;
exports.selectLimit = filterLimit;
exports.selectSeries = filterSeries;
exports.wrapSync = asyncify;

Object.defineProperty(exports, '__esModule', { value: true });

})));
;
/* Functionality Related to Barne and Nobles Users */

var activeForecastSKU = null;
var activeForecastType = null;

$(function () {
    BNReadOnly();
});

function enableBNCategoryEdit() {
    return;

    $(".catLocDivAll").each(function() {
        var sku = $(this).attr("data-sku");
        $(this).webuiPopover({
            type: "async",
            cache: false,
            url: "/GetTreelineControl.aspx?controlName=/uc/bn/TitleFrameBNInfoCatSelect.ascx&sku=" + sku + "&storeID=All",
            async: {
                success: function () {
                    ActivateLineItemDeptBN(sku, "All");
                }
            }
        });
    });
}

function ShowBNForecastImportDialog(catalogID, org) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/bn/export/BNImport.ascx&catalogID=" + catalogID + "&org=" + org;
    openModal(url, "400px", "200px");
}

function openBNCategoryExplorer(sku, saveTargetId, storeId) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/bn/BNCategoryExplorer.ascx&sku=" + sku + "&saveTargetId=" + saveTargetId + "&storeId=" + storeId;
    openModal(url, "500px", "400px");
}

function saveBNCategoryTextToInput(targetId, storeId, sku, selectedCategory) {
    $("#catLocDiv_" + storeId + "_" + sku).webuiPopover("show");
    $("#" + targetId).val(selectedCategory);
}

function ExportBNData(catalogID, org) {
    $('body')
        .append("<div id=\"BNDialog\" title=\"Export Forecast\" style=\"display: none;\"><span class=\"icon-close-icon iconSVG_Darker\" onclick=\"$('#BNDialog').dialog('close');$('#BNDialog').remove();\" style=\"position: absolute; right: 18px; top: 17px;\"></span><p class=\"textMedium\">Download forecast data to Excel?</p></div>");
    $("#BNDialog")
        .dialog(
        {
            create: function (event, ui) {
                $("#BNDialog").css('visibility', '');
            },
            title: "Export Forecast",
            autoOpen: true,
            modal: true,
            dialogClass: "no-close bnExportDialog",
            resizable: false,
            buttons: [
            {
                text: "No",
                "class": "medGrayButton textMedium",
                click: function () {
                    $(this).dialog('close');
                    $("#BNDialog").remove();
                }
            },
            {
                text: "Yes",
                "class": "medGrayButton textMedium",
                click: function () {
                    window.open("/uc/bn/export/BNExport.aspx?org=" + org + "&" + "catalogID=" + catalogID, "export");
                    $(this).dialog("close");
                    $("#BNDialog").remove();
                }
            }
            ]
        }
     );

}

function saveBNCategory(sku, storeId) {
    var originalValue = $("#catLoc_" + storeId + "_" + sku).html();
    var newValue = $("#inputCatLoc_" + storeId + "_" + sku).val();
    $("#catLoc_" + storeId + "_" + sku).show();
    if (originalValue != newValue) {
        var values = {
            storeID: storeId,
            sku: sku,
            category: encodeURI(newValue),
            originalCategoryCode: originalValue,
            confirmed: false
        };

        $.getJSON("/getJSONData.aspx?m=BN&builder=AssignProductCategory", values, function (data) {
            if (data.code == "CONFIRM") {
                if (confirm(data.text)) {
                    values.confirmed = true;
                    $.getJSON("/getJSONData.aspx?m=BN&builder=AssignProductCategory",
                        values,
                        function (data) {
                            if (data.code == "SUCCESS") {
                                saveToOrgCategory(sku, storeId, originalValue, newValue, values);
                                updateBNTitleInfo(sku, data);
                            } else {
                                $("#catLocDiv_" + storeId + "_" + sku).webuiPopover('hide');
                            }
                        });
                } else {
                    $("#catLocDiv_" + storeId + "_" + sku).webuiPopover('hide');
                }
            }
            else {
                if (data.code == "SUCCESS") {
                    saveToOrgCategory(sku, storeId, originalValue, newValue, values);
                    updateBNTitleInfo(sku, data);
                } else {
                    $("#catLocDiv_" + storeId + "_" + sku).webuiPopover('hide');
                }
            }
        });
    }
}

function saveToOrgCategory(sku, storeId, originalValue, newValue, values) {
    $.getJSON("/getJSONData.aspx?builder=SaveOrganizationCategory", values, function (data) {
        if (data[0].code == "SUCCESS") {
            if (storeId == "All") {
                if (newValue == "") {
                    var mappedCategory = $("#mappedCat" + sku).val();
                    $("#catLoc_" + storeId + "_" + sku).html(mappedCategory);
                    if (mappedCategory != "") {
                        $("#catAuto_" + sku).show();
                        $("#inputCatLoc_" + storeId + "_" + sku).val(mappedCategory);
                    } else {
                        $("#catLoc_" + storeId + "_" + sku).html('<%=GetLocalizedString("add_category")%>')
                    }
                } else {
                    $("#catAuto_" + sku).hide();
                }
            } else {
                if (newValue == "") {
                    $("#catLocDisplay_" + storeId + "_" + sku).hide();
                } else {
                    $("#catLocDisplay_" + storeId + "_" + sku).show();

                }
            }
            $("#catLocDiv_" + storeId + "_" + sku).webuiPopover('hide');
            $("#catLoc_" + storeId + "_" + sku).html($("#inputCatLoc_" + storeId + "_" + sku).val());
        }
    });
}


function removeBNCategory(sku, storeId) {
    $("#inputCatLoc_" + storeId + "_" + sku).val("");
    saveBNCategory(sku, storeId);
}

function updateBNTitleInfo(sku, data) {
    var d = data.text.split(',');
    $("#catLoc_All_" + sku).attr("title", d[0]);

    $("#" + sku + "_buyer").text(d[1]);
    $("#" + sku + "_buyer").show();
    $("#" + sku + "_buyerLabel").show();
}

function ActivateLineItemDeptBN(sku, storeId) {
    LoadBNCategories("inputCatLoc_" + storeId + "_" + sku, storeId, sku);

    var currentCategory = $("#catLoc_" + storeId + "_" + sku).html(),
        categoryInput = $("#inputCatLoc_" + storeId + "_" + sku),
        categoryValue = currentCategory != '' && currentCategory != '<%=GetLocalizedString("add_category")%>' ? currentCategory : "",
        selectedValue = $("#selectedCat" + sku).val();
    
    if (currentCategory === selectedValue || selectedValue === "" ) {
        categoryInput.val(categoryValue).select().focus();
    } else {
        categoryInput.val(selectedValue);
        // Reset for next open
        $("#selectedCat" + sku).val("");
    }
}

function LoadBNCategories(autoid, storeId, sku) {
    if (storeId != undefined && $("input#Department_Categories").data("storeID") == storeId) {
        LoadBNDepartments(autoid, sku, storeId);
    }
    else {
        $("input#Department_Categories").val("");

        var passedStore = storeId;
        if (storeId == "All") {
            passedStore = "";
        }
        $.get("/getJSONData.aspx?m=BN&builder=GetStoreCategories", { storeID: passedStore }, function (data) {
            if (data != "EMPTY") {
                if ($('#Department_Categories').length == 0)
                    $('body').append("<input type='hidden' value='' id='Department_Categories' />");
                $("input#Department_Categories").data("storeID", storeId);
                $("input#Department_Categories").val(data);

                LoadBNDepartments(autoid, sku, storeId);
            }
        });
    }

    $('#Cat_Dept_Auto_Loading').hide();
}

function LoadBNDepartments(autoid, sku, storeId) {
    var data = $("input#Department_Categories").val();
    if (data) {
        var list = data.split("~");
        $("#" + autoid).autocomplete(list, {
            matchContains: true,
            max: 200
        }).result(function (event, item, formatted) {
            saveBNCategory(sku, storeId);
            return false;
        });
        $("#inputCatLoc_" + storeId + "_" + sku).prop('disabled', false);
        $("#inputCatLoc_" + storeId + "_" + sku).focus();
    }
}

function ShowBNManageTitle(sku) {
    var cat = $('#' + sku + '_cat_orig').val();
    cat = encodeURIComponent(cat);
    var url = "/GetTreelineControl.aspx?controlName=/uc/bn/Manage_Title_BN.ascx&sku=" + sku + "&cat=" + cat;
    openModal(url, "600px", "450px");
}

function ShowBNNoteDialog(sku) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/bn/BNNotesDialog.ascx&sku=" + sku;
    openModal(url, "600px", "450px");
}

function OpenBNAccessV2(ean, sku, userid, selectedOrg, selectedMailingId) {
    var left = (screen.width / 2) - (w / 2);
    var top = (screen.height / 2) - (h / 2);
    url = '/uc/bn/BNAccessV2.aspx?ean=' + ean + '&selectedMailingId=' + selectedMailingId;
    var title = 'bnaccess';
    var w = 1200;
    var h = 900;
    var popup = window.open(url, title, 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=yes, resizable=yes, copyhistory=no, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left);
    updateSharedNoteForTitleSelected(popup, sku, userid, selectedOrg);
}

function OpenBNAccess(ean, sku) {
    var left = (screen.width / 2) - (w / 2);
    var top = (screen.height / 2) - (h / 2);
    url = '/uc/bn/BNAccess.aspx?ean=' + ean;
    var title = 'bnaccess';
    var w = 1200;
    var h = 900;
    window.open(url, title, 'toolbar=no, location=no, directories=no, status=no, menubar=no, scrollbars=yes, resizable=yes, copyhistory=no, width=' + w + ', height=' + h + ', top=' + top + ', left=' + left);
}

function updateSharedNoteForTitleSelected(window, sku, userid, selectedOrg) {
    var interval = setInterval(function () {
        if (window.closed) {
            clearInterval(interval);
            $.getJSON("/getJSONData.aspx?m=BN&builder=GetBNSharedNote", { sku: sku, userid: userid, orgid: selectedOrg }, function (data) {
                if (data.code == "OK") {
                    //fix the date
                    $('#note_' + sku).html("<span class=\"noteDate\">(" + data.data + ")</span>&nbsp;" + data.text + "");
                }
                else {
                    //throw an error
                }
            });
        }
    },
    1000
 );
}

function _fcEnter(e, sku, type) {
    var evtobj = window.event ? event : e
    var unicode = e.keyCode ? e.keyCode : e.charCode
    if (unicode == 13) {
        CloseForecastEdit(null, null, null);
    }
}

function BNReadOnly() {
    var tables = $("[id^=forecast_table_]");
    tables.each(function () {
        var rows = $(this).children('tbody').children('tr');
        rows.each(function () {
            $(this).prop('onclick', null);
            $(this).prop('ondblclick', null);
        });
    });
    tables = $("[id^=initial_buy_table_]");
    tables.each(function () {
        var rows = $(this).children('tbody').children('tr');
        rows.each(function () {
            $(this).prop('onclick', null);
            $(this).prop('ondblclick', null);
        });
    });
    $("[id^='catLocDiv_All_'").webuiPopover().off();
    $("[id^='catLocDiv_All_'").css("cursor", "default");

    $("[id^='catPin_']").hide();

    $("[id^='BNEditTitle_'").hide();
    $("[id^='BNForecast_'").hide();

    $(".BNImport").hide();
    $(".BNExport").hide();

    if (jQuery.isFunction(ShowBNNoteDialog)) {
        ShowBNNoteDialog = null;
    }
    if (jQuery.isFunction(ShowBNManageTitle)) {
        ShowBNManageTitle = null;
    }
}

function forecastGridHide() {
    for (var index = 9; index <= 13; index++) {
        $('.bnforecastgridclasshack td:nth-child(' + (index + 1) + ')').hide();
        $('.bnforecastgridclasshack th:nth-child(' + (index + 1) + ')').hide();
    }
    $('.bnforecastgridclasshack').css("position", "").css("z-index", "auto");
    $('#page-cover').hide();
    $('#page-cover').off('click').off('touchstart');
    return true;
}

function forecastGridShow() {
    for (var index = 9; index <= 13; index++) {
        $('.bnforecastgridclasshack td:nth-child(' + (index + 1) + ')').show();
        $('.bnforecastgridclasshack th:nth-child(' + (index + 1) + ')').show();
    }
    $('.bnforecastgridclasshack').css("position", "absolute").css("z-index", 999);
    if (isMobile) {
        $(this).off('touchstart');
        BindBodyOnClick(isMobile);
    }
    else {
        $(this).off('click');
        BindBodyOnClick(isMobile);
    }
    return false;
}

function BindBodyOnClick(isMobile) {
    $('body').append('<div id="page-cover"></div>');
    $("#page-cover").show().css("opacity", 0.6);
    if (isMobile) {
        $('#page-cover').on({
            touchstart: function () {
                CloseForecastEdit(null, null, null);
                forecastGridHide();
            }
        });

    }
    else {
        $('#page-cover').on({
            click: function () {
                CloseForecastEdit(null, null, null);
                forecastGridHide();
            }
        });
    }

}

function CloseForecastEdit(elem, sku, type) {
    if (activeForecastSKU != null && (activeForecastSKU != sku || activeForecastType != type)) {
        $(".fcInput input").css("background-color", "#ffc321");
        $.getJSON("/GetJSONData.aspx?m=BN&builder=SaveBNForecast", $("#forecast_" + activeForecastSKU).find("input").serialize(),
                function (data) {
                    if (data.code == "ERROR") {
                        CloseForecastInputs();
                    }
                    else {
                        CalculateTotals(activeForecastSKU, activeForecastType);
                        CloseForecastInputs();
                        activeForecastSKU = null;
                        activeForecastType = null;
                    }
                });
    }
}

function CalculateTotals(sku, type) {
    var fourWeekTotal = 0;
    var eightWeekTotal = 0;
    for (i = 1; i <= 13; i++) {
        var val = $('#fc_' + sku + '_' + type + '_' + i).val();
        if (!isNaN(parseFloat(val)) && isFinite(val)) {
            if (i <= 4) fourWeekTotal += parseInt(val);
            eightWeekTotal += parseInt(val);
        }
    }
    $('#fourWeekTotal_' + type + '_' + sku).text(fourWeekTotal);
    $('#eightWeekTotal_' + type + '_' + sku).text(eightWeekTotal);
}

function CloseForecastInputs() {
    $(".fcInput").each(function (index) {
        var val = $(this).find("input").val();
        $(this).removeClass("fcInput").text(val);
    });
}

function ToggleForecastEdit(tableID, elem, sku, type) {
    if ($(elem).find(".fcInput").length == 0) {
        CloseForecastEdit(null, sku, type);
        $("#" + tableID + " tr.fc_" + sku + "_" + type + " td.editable").each(function (index) {
            var inputID = "fc_" + this.id;
            var html = "<input id='" +
                inputID +
                "' name='" +
                inputID +
                "' style='width:28px;' type='text' value='" +
                $(this).text().trim() +
                "' onkeyup=\"_fcEnter(event,'" +
                sku +
                "', '" +
                type +
                "');\"/>";
            html += "<input name='" + inputID + "_orig' type='hidden' value='" + $(this).text().trim() + "'/>"
            $(this).addClass("fcInput").html(html);
        });
        activeForecastSKU = sku;
        activeForecastType = type;
    }
}

function setPatternEan(sku, newPatternEan, currentPatternEan, auditUserId, selectedMailingId, source) {
    if (sku && newPatternEan && currentPatternEan) {
        // Request change of PatternEan if there is a new value or if the comp is default (index 0)
        if(newPatternEan !== currentPatternEan || !auditUserId) {
            $.post("/getJSONData.aspx?m=CompProduct&builder=SetPatternEan", {sku: sku, patternEan: newPatternEan, selectedMailingId: selectedMailingId}, 
                function(data) {
                    if (data.code == "SUCCESS"){
                        var mailingId = getListViewProperty("selectedMailingID");
                        if ($(".cbd").length == 0) {
                            renderCompDetail(sku, source, 0, mailingId);
                        } else {
                            renderCompDetail(sku, source, 1, mailingId);
                        }
                    } else {
                        alert(data.text);
                    }
                }, "json");
        }
    }
}

function onBNTitleEditIconClick(sku, isBarnesAndNoble) {
    if (isBarnesAndNoble) {
        ShowBNManageTitle(sku);
    } else {
        alert(getRes('bn_title_edit_unauthorized'));
    }
};
﻿var HasTooltipDataBeenPopulated = {};

function calculateInvIndex(data, dashType, sku) {
    var invIndex = 0;
    var weightedCirc = data.weightedLastCirculationDaysSince * 1;
    if (weightedCirc > 0) {
        invIndex = weightedCirc;
    }
    return invIndex;
}
function updateTooltipHtmlUsingModel(model, dashType, sku) {
    // if the model value is a boolean, it is used to hide/show the html element
    // if the model value isn't a boolean, it is used to populate/modify the html element
    for (var attr in model) {
        if (model[attr] === false) {
            $(".data-tooltip-" + attr + "-" + dashType + "-" + sku).hide();
        } else {
            $(".data-tooltip-" + attr + "-" + dashType + "-" + sku).show();
            if (attr === "flameColor") {
                $(".data-tooltip-flame-" + dashType + "-" + sku).css("color", model[attr]);
            } else {
                if (model[attr] !== true) {
                    $(".data-tooltip-" + attr + "-" + dashType + "-" + sku).html(model[attr]);
                }
            }
        }
    }
}

// Converts an integer number of days to a string telling how many
// years, months and days that number of days spans.
function getLastActivityString(numDaysAgo) {
    if (typeof (numDaysAgo) === "undefined" || numDaysAgo === null || isNaN(numDaysAgo)
        || numDaysAgo < 0) {
        return "unavailable";
    }
    if (numDaysAgo === 0) {
        return "less than a day ago";
    }
    
    var now = moment();
    var lastActivity = moment(now).subtract(numDaysAgo, 'days');

    var years = now.diff(lastActivity, 'years');
    lastActivity.add(years, 'years');
    var months = now.diff(lastActivity, 'months');
    lastActivity.add(months, 'months');
    var days = now.diff(lastActivity, 'days');

    var lastActivityStr = "";
    if (years > 0) {
        // "y years, m months"
        lastActivityStr += years;
        lastActivityStr += (years > 1) ? " years, " : " year, ";
        lastActivityStr += months;
        lastActivityStr += (months > 1 || months === 0) ? " months" : " month";
    } else if (months > 0) {
        // "m months and d days"
        lastActivityStr += months;
        lastActivityStr += (months > 1) ? " months and " : " month and ";
        lastActivityStr += days;
        lastActivityStr += (days > 1 || days === 0) ? " days" : " day";
    } else {
        // "d days"
        lastActivityStr += days
        lastActivityStr += (days > 1) ? " days" : " day";
    }
    lastActivityStr += " ago";
    return lastActivityStr;
}

function populateTooltipStockDataForActivityMode(data, dashType, sku, isLibrary) {
    var stockDataModel = {
        hasActivity: false,
        hasNoActivity: true,
    };

    if (data) {
        var lastRecdFormatted = formatDotNetDateTimeUsingLocale(data.lastCheckedInDateTime);
        var lastSoldFormatted = formatDotNetDateTimeUsingLocale(data.lastCirculationDateTime);
        stockDataModel.lastRecd = lastRecdFormatted;
        stockDataModel.lastSold = lastSoldFormatted;

        if (data.lifetimeCirculation) {
            stockDataModel.lifetimeSales = data.lifetimeCirculation;
        } else {
            stockDataModel.lifetimeSales = "-";
        }

        if (data.lastYearSales) {
            stockDataModel.lastYearSales = data.lastYearCirculation;
        } else {
            stockDataModel.lastYearSales = "-";
        }

        stockDataModel.currentOnHand = data.onHand;
        stockDataModel.currentOnOrder = data.onOrder;
        stockDataModel.hasNoActivity = false;
        stockDataModel.hasOnOrder = false;

        stockDataModel.activityIndex = calculateInvIndex(data, dashType, sku);
        stockDataModel.daysSinceLastActivity = data.daysSinceLastActivity;

        if (data.lastCheckedInDateTime ||
            data.lastCirculationDateTime ||
            data.onOrder > 0 ||
            data.onHand > 0) {

            stockDataModel.hasActivity = true;
            if (stockDataModel.currentOnOrder > 0) {
                stockDataModel.hasOnOrder = true;
            }

            if (isLibrary) {
                var activityInType = "Checked in";
                var activityOutType = "Checked out";
                var noActivityString = "No Checkout History";
            } else {
                var activityInType = "Received";
                var activityOutType = "Sold";
                var noActivityString = "No Sales History";
            }

            var now = moment().startOf('day');
            var lastSoldExists = false, lastRecdExists = false;
            if (data.lastCheckedInDateTime) {
                var lastReceivedDate = moment(data.lastCheckedInDateTime).startOf('day');
                var lastReceivedActivityString = activityInType + " " + lastRecdFormatted;
                lastRecdExists = true;
            }
            if (data.lastCirculationDateTime) {
                var lastSoldDate = moment(data.lastCirculationDateTime).startOf('day');
                var lastSoldActivityString = activityOutType + " " + lastSoldFormatted;
                lastSoldExists = true;
            }

            if (lastRecdExists && !lastSoldExists) {
                stockDataModel.lastActivityString = lastReceivedActivityString;
            } else if (lastSoldExists && !lastRecdExists) {               
                stockDataModel.lastActivityString = lastSoldActivityString;
            } else if (lastSoldExists && lastRecdExists) {
                if (lastSoldDate.isAfter(lastReceivedDate)) {
                    stockDataModel.lastActivityString = lastSoldActivityString; 
                } else {
                    stockDataModel.lastActivityString = lastReceivedActivityString;
                }
            } else {
   
                stockDataModel.lastActivityString = noActivityString;
            }
        } else {
            stockDataModel.hasNoActivity = true;
        }
    }

    if (EdelweissAnalytics.filterOptions[dashType].includeTitlesFromAllStockAnalysisClasses) {
        var stockAnalysisClass = getStockAnalysisClassForActivityMode(data, dashType, sku);
        
    } else {
        var stockAnalysisClass = EdelweissAnalytics.filterOptions[dashType].stockAnalysisClass;
    }
    stockDataModel.flameColor = EdelweissAnalytics.filterOptions[dashType].wedgeColors[
        stockAnalysisClass];

    updateTooltipHtmlUsingModel(stockDataModel, dashType, sku);
}

function populateTooltipStockDataForTurnMode(data, dashType, sku, isLibrary) {
    var stockDataModel = {
        hasTurn: false,
        noTurn: false,
        neverStocked: false
    };
    if (data) {
        var currencyCode = 'USD';
        if (window.cultureName === "en-GB") {
            currencyCode = 'GBP';
        } else if (window.cultureName === "fr-FR" || window.cultureName === "de-DE") {
            currencyCode = 'EUR';
        }
        if (data.hasOwnProperty("totalSalesForInterval") && data.totalSalesForInterval > 0) {
            var totalSales = data.totalSalesForInterval;
            if (isLibrary) {
                stockDataModel.totalSales = totalSales;
            } else {
                stockDataModel.totalSales = totalSales.toLocaleString(window.cultureName, { style: 'currency', currency: currencyCode }).slice(0, -1);
            }
        }
        if (data.hasOwnProperty("totalInventoryForInterval") && data.totalInventoryForInterval > 0) {
            var totalInventory = data.totalInventoryForInterval;
            if (isLibrary) {
                stockDataModel.totalInventory = totalInventory;
            } else {
                stockDataModel.totalInventory = totalInventory.toLocaleString(window.cultureName, { style: 'currency', currency: currencyCode }).slice(0, -1);
            }
        }
        if (data.hasOwnProperty("annualizedTurn") && data.annualizedTurn > 0) {
            stockDataModel.hasTurn = true;
            stockDataModel.turn = data.annualizedTurn;
        }
    }
    var stockAnalysisClass = EdelweissAnalytics.stockAnalysisClassUsingTurn.neverStocked;
    if (EdelweissAnalytics.filterOptions[dashType].includeTitlesFromAllStockAnalysisClasses) {
         stockAnalysisClass = getStockAnalysisClassForTurnMode(data, dashType);
    } else {
         stockAnalysisClass = EdelweissAnalytics.filterOptions[dashType].stockAnalysisClass;
    }
    if (!stockDataModel.hasTurn) {
        stockDataModel.turn = 0;
        if (stockAnalysisClass == EdelweissAnalytics.stockAnalysisClassUsingTurn.noSales) {
            stockDataModel.noTurn = true;
        } else {
            stockDataModel.neverStocked = true;
        }
    }
       
    stockDataModel.flameColor = EdelweissAnalytics.filterOptions[dashType].wedgeColors[stockAnalysisClass];
    updateTooltipHtmlUsingModel(stockDataModel, dashType, sku);
}

function getStockAnalysisClassForActivityMode(data, dashType, sku) {
    var stockAnalysisClass = 0;
    if (typeof data !== "undefined" && data !== null) {
        if (data.hasOwnProperty("onHand") && data.onHand > 0) {
            var activityIndex = calculateInvIndex(data, dashType, sku);
            if (activityIndex <= EdelweissAnalytics.filterOptions[dashType].inventoryIndexRangeLower) {
                // fresh
                stockAnalysisClass = 2;
            } else if (activityIndex >= EdelweissAnalytics.filterOptions[dashType].inventoryIndexRangeUpper) {
                // stale
                stockAnalysisClass = 4;
            } else {
                // stable
                stockAnalysisClass = 3;
            }
        } else {
            if (data.hasOwnProperty("onOrder") && data.onOrder > 0) {
                stockAnalysisClass = 1;
            } else {
                stockAnalysisClass = 0;
            }
        }
    }
    return stockAnalysisClass;
}

function getStockAnalysisClassForTurnMode(data, dashType) {
    var stockAnalysisClass = EdelweissAnalytics.stockAnalysisClassUsingTurn.neverStocked;
    if (!data) {
        return stockAnalysisClass;
    }    
    if (data.annualizedTurn > EdelweissAnalytics.filterOptions[dashType].turnRangeUpper) {
        stockAnalysisClass = EdelweissAnalytics.stockAnalysisClassUsingTurn.highTurn;    
    } else if (data.annualizedTurn > EdelweissAnalytics.filterOptions[dashType].turnRangeLower) {
        stockAnalysisClass = EdelweissAnalytics.stockAnalysisClassUsingTurn.mediumTurn;
    } else if (data.annualizedTurn > 0) {
        stockAnalysisClass = EdelweissAnalytics.stockAnalysisClassUsingTurn.lowTurn;
    } else {        
        stockAnalysisClass = EdelweissAnalytics.stockAnalysisClassUsingTurn.noSales;
    }
           
    return stockAnalysisClass;
}

function populateTooltipMarketData(data, dashType, sku) {
    var marketDataModel = {
        hasMarketData: false,
        hasNoMarketData: false,
        marketSales: 0,
        hasMarketSales: false,
        marketOH: 0,
        hasMarketOH: false,
        marketOO: 0,
        hasMarketOO: false,
        hasMarketOHOrSales: false
    };

    if (data) {
        if (data.storesSoldPercent) {
            marketDataModel.marketSales = data.storesSoldPercent;
        }
        if (data.storesOnHandPercent) {
            marketDataModel.marketOH = data.storesOnHandPercent;
        }
        if (data.storesOrderedPercent) {
            marketDataModel.marketOO = data.storesOrderedPercent;
        }
        if (marketDataModel.marketSales + marketDataModel.marketOH + marketDataModel.marketOO == 0) {
            marketDataModel.hasNoMarketData = true;
        } else {
            marketDataModel.hasMarketData = true;
            if (marketDataModel.marketSales > 0) {
                marketDataModel.hasMarketSales = true;
                marketDataModel.marketSales = Math.round(marketDataModel.marketSales * 100);
            }
            if (marketDataModel.marketOH > 0) {
                marketDataModel.hasMarketOH = true;
                marketDataModel.marketOH = Math.round(marketDataModel.marketOH * 100);
            }
            if (marketDataModel.marketOO > 0) {
                marketDataModel.hasMarketOO = true;
                marketDataModel.marketOO = Math.round(marketDataModel.marketOO * 100);
            }
        }
    } else {
        marketDataModel.hasNoMarketData = true;
    }

    if (marketDataModel.hasMarketOH || marketDataModel.hasMarketSales) {
        marketDataModel.hasMarketOHOrSales = true;
    }
    updateTooltipHtmlUsingModel(marketDataModel, dashType, sku);
}

function populateTooltipOnShow($element) {
    // retrieve uniqueness parameters
    var sku = $element.find(".uniquenessParameters").attr("data-sku");
    var dashType = $element.find(".uniquenessParameters").attr("data-dashType");
    var isLibrary = $element.find(".uniquenessParameters").attr("data-isLibrary") === "true";

    var marketId = EdelweissAnalytics.filterOptions[dashType].marketFilters[0];

    if (marketId == null) {
        ///Replace this with a call to get the user's market preference
        marketId = 0;
    }
    var storeId = "";
    if (!EdelweissAnalytics.filterOptions[dashType].peerOrgId && !EdelweissAnalytics.filterOptions[dashType].peerBranchId) {
        storeId = EdelweissAnalytics.filterOptions[dashType].locationFilters[0];
    } else if (EdelweissAnalytics.filterOptions[dashType].peerOrgId) {
        storeId = EdelweissAnalytics.filterOptions[dashType].peerOrgId + "A";
    } else {
        storeId = EdelweissAnalytics.filterOptions[dashType].peerBranchId;
    }
   
    var monthsBack = EdelweissAnalytics.filterOptions[dashType].monthsBack;
    var referenceCode = EdelweissAnalytics.filterOptions[dashType].referenceCode;
    var attributeFilters = EdelweissAnalytics.filterOptions[dashType].attributeFilters;
    var segmentationMode = EdelweissAnalytics.filterOptions[dashType].segmentationMode;

    if (HasTooltipDataBeenPopulated[dashType + '-' + sku]) {
        return;
    }

    var params = {
        marketId: marketId,
        monthsBack: monthsBack,
        segmentationMode: segmentationMode
    };

    if (storeId != null) {
        params.storeId = storeId;
    }

    if (referenceCode === EdelweissAnalytics.referenceCodes.FamilyView) {
        params.isFamilyMode = true;
    }

    if (attributeFilters != null && attributeFilters.length > 0) {
        params.attributeFilters = JSON.stringify(attributeFilters);
    }
    
    $.ajax({
        cache: false,
        url: "/api/v1/analysis/" + sku,
        data: params,
        contentType: "application/json",
        success: function (data) {
            populateTooltipMarketData(data.marketAnalysis, dashType, sku);
            if (segmentationMode !== EdelweissAnalytics.segmentationModes.Turn) {
                populateTooltipStockDataForActivityMode(data.stockAnalysis, dashType, sku, isLibrary);
            } else {
                populateTooltipStockDataForTurnMode(data.stockAnalysis, dashType, sku, isLibrary);              
            }

            var classesToFadeIn = [
                ".tooltipValue",
                ".tooltipFlameContainer",
                ".activityIndexSummaryContainer"
            ];
            classesToFadeIn.forEach(function(classToFadeIn) {
                $element.find(classToFadeIn).css('visibility', 'visible').hide().fadeIn({
                    duration: 750,
                    easing: 'easeInExpo'
                });
            });
            HasTooltipDataBeenPopulated[dashType + '-' + sku] = true;
        }
    });
}
function showTooltipDetails(elem) {
    $(".toolTip-details-" + elem).show('fast');
}

function hideTooltipDetails(elem) {
    $(".toolTip-details-" + elem).hide('fast');
};
/**
 * @class InfiniteSlider
 * The infinite slider library is used to create a slider that could potentially contain an infinite number of elements.
 * It will load in an initial number of elements, and then each time the user scrolls near the end of the slider
 * it will call an API endpoint which will provide it with the new elements to add to the slider.
 */
var InfiniteSlider = function(slider) {
    /** {string} the id of an empty div into which the slider should be inserted */
    this.containerId = slider.containerId;
    /** {string} A string to show in place of the slider when it's loading the initial elements*/
    this.initialLoadingText = slider.initialLoadingText;

    /** the api must return a list of elements */

    /** {string} url to access the api **/
    this.apiUrl = slider.apiUrl;
    /** {object} a JSON object of parameters to add to the api url */
    this.apiUrlParams = slider.apiUrlParams;
    /** {string} the http method to access the api (GET, POST, ...) */
    this.apiMethod = slider.apiMethod;
    /** {object} a JSON object of data to pass to the api endpoint */
    this.apiData = slider.apiData;

    /** variables instantiated privately */
    this.sliderId = slider.containerId + "-infiniteSlider";
    this.prevButtonId = this.sliderId + "-prevButton";
    this.nextButtonId = this.sliderId + "-nextButton";
    this.loadingAnimationId = this.sliderId + "-loadingAnimation";
    this.lowerIndex = 0;
    this.upperIndex = 0;
    this.numberOfInitialElements = 30;
    this.numberOfElementsToAdd = 20;
    this.isLoadingElements = false;
    this.foundEndOfElements = false;

    this.initialize();
};

/**
 * Initializes the slider with elements, adds the prev and next buttons, and starts
 * listening to the scroll event for when to add new elements.
 */
InfiniteSlider.prototype.initialize = function () {
    var self = this;

    var sliderHtml = "<div class='infiniteSlider' id='" + self.sliderId + "'></div>";
    $("#" + self.containerId)
        .empty()
        .css("position", "relative")
        .append(sliderHtml);

    self.getAndAddElementsToSlider(true).then(function () {
        self.initializePrevAndNextButtons();
    });

    var sliderElement = $("#" + self.sliderId)[0];
    $("#" + self.sliderId).scroll(function () {
        if (!self.foundEndOfElements && !self.isLoadingElements
                    && sliderElement.scrollLeft >= 0.8 * (sliderElement.scrollWidth - sliderElement.clientWidth)) {
            self.getAndAddElementsToSlider(false);
        }
    });            
};

/**
 * Gets the elements from the API and adds them to the slider, while handling the loading animation.
 * @param {boolean} isInitialLoad - Lets us know if this is the first time loading elements into the slider
 * @returns {promise} donePromise - Lets us know when the slider is done loading the elements to retrieve
 */
InfiniteSlider.prototype.getAndAddElementsToSlider = function (isInitialLoad) {
    var self = this;
    var donePromise = new Promise(function (resolve, reject) {
        self.isLoadingElements = true;
        self.showLoadingAnimation(isInitialLoad);
        self.getElementsInWindow(isInitialLoad).then(function (elementsData) {
            var elementHtmlList = elementsData.elementHtmlList;
            var indexList = elementsData.indexList;
            self.foundEndOfElements = elementsData.foundEndOfElements;
            self.hideLoadingAnimation();
            self.addElementsToSlider(elementHtmlList, indexList);
            self.isLoadingElements = false;
            resolve(null);
        }).catch(function (reason) {
            self.hideLoadingAnimation();
            self.isLoadingElements = false;
            reject(reason);
        });
    });
    return donePromise;
};

/**
 * Gets the elements from the API
 * @param {boolean} isInitialLoad
 * @returns {promise} elementsDataPromise - A promise that eventually contains the elements being retrieved
 */
InfiniteSlider.prototype.getElementsInWindow = function (isInitialLoad) {
    var self = this;
    var elementsDataPromise = new Promise(function (resolve, reject) {
        if (isInitialLoad) {
            var windowSize = self.numberOfInitialElements;
        } else {
            var windowSize = self.numberOfElementsToAdd;
        }

        var newUpperIndex = self.upperIndex + windowSize;
        var windowStartIndex = self.upperIndex;
        self.upperIndex = newUpperIndex;

        var params = {
            start: windowStartIndex,
            rows: windowSize
        };
        $.extend(params, self.apiUrlParams);

        $.ajax({
            type: self.apiMethod,
            url: self.apiUrl + "?" + $.param(params),
            data: self.apiData,
            dataType: "json"
        }).done(function (elementsData) {
            if (!elementsData.hasOwnProperty("elementHtmlList")) {
                reject("elementsData did not contain an elementHtmlList property.");
            } else if (!elementsData.hasOwnProperty("foundEndOfElements")) {
                reject("elementsData did not contain a foundEndOfElements property.");
            } else if (!Array.isArray(elementsData.elementHtmlList)) {
                reject("elementsHtmlList was not an array.");
            } else if (!Array.isArray(elementsData.indexList)) {
                reject("indexList was not an array.");
            } else if (elementsData.elementHtmlList.length !== elementsData.indexList.length) {
                reject("indexList and elementHtmlList must be the same length.");
            } else {
                resolve(elementsData);
            }
        }).fail(function (jqXHR, textStatus) {
            reject("Slider failed to get data from the API.");
        });
    });
    return elementsDataPromise;
};

/**
 * Adds the elements to the slider
 * @param {String[]} elementHtmlList - each string in the list is the html for a particular element
 * @param {String[]} indexList - a list of unique ids, based on position in list, to uniquely identify each element in the slider
 */
InfiniteSlider.prototype.addElementsToSlider = function(elementHtmlList, indexList) {
    var self = this;
    var newSlidesHtml = "";
    for (var i = 0; i < elementHtmlList.length; i++) {
        newSlidesHtml += "<div class='sliderElement' data-index='" + indexList[i] + "'>"
            + elementHtmlList[i] + "</div>";
    }
    $("#" + self.sliderId).append(newSlidesHtml);
};

/**
 * Adds the html for the prev and next buttons to the slider and starts each of them
 * listening for click events, to scroll left or right.
 */
InfiniteSlider.prototype.initializePrevAndNextButtons = function () {
    var self = this;

    var prevButtonHtml = "<div id='" + self.prevButtonId + "' " +
            "class='icon-navi-left-icon iconSVG_Darker infiniteSlider-prev'></div>";
    var nextButtonHtml = "<div id='" + self.nextButtonId + "' " +
            "class='icon-navi-right-icon iconSVG_Darker infiniteSlider-next'></div>";
    $("#" + self.containerId).append(prevButtonHtml + nextButtonHtml);

    var sliderElement = document.getElementById(self.sliderId);
    $("#" + self.prevButtonId).on("click", function () {
        $("#" + self.sliderId).animate({ scrollLeft: sliderElement.scrollLeft - 600 }, 300);
    });
    $("#" + self.nextButtonId).on("click", function () {
        $("#" + self.sliderId).animate({ scrollLeft: sliderElement.scrollLeft + 600 }, 300);
    });
};

/**
 * Forms and adds the loading animation to the end of the slider
 * @param {boolean} isInitialLoad
 */
InfiniteSlider.prototype.showLoadingAnimation = function (isInitialLoad) {
    if (isInitialLoad) {
        $("#" + this.sliderId).append("<div class='sliderElement' id='"
            + this.loadingAnimationId + "'>"
            + this.initialLoadingText + "...</div>");
    } else {
        $("#" + this.sliderId).append("<div class='sliderElement' id='"
            + this.loadingAnimationId + "'>"
            + "<img src='images/loading.gif' style='position:absolute; top:40%;'></div>");
    }
};

/**
 * Removes the loading animation from the slider
 */
InfiniteSlider.prototype.hideLoadingAnimation = function () {
    $("#" + this.loadingAnimationId).remove();
};

InfiniteSlider.hideSliderElement = function (sliderId, index) {
    var $sliderElement = $("#" + sliderId + " .sliderElement[data-index='" + index + "']");
    if ($sliderElement) {
        $sliderElement.hide();
    } else {
        console.error("The slider or slider element does not exist - based on the provided sliderId and index.");
    }
};;
EdelweissAnalytics.CrossStoreView = new (function () {
    var _this = this;

    this.records = [];
    this.exportRecords = [];

    this.initialize = function () {
        var params = EdelweissAnalytics.CrossStoreView.getParams();
        if (params !== null) {
            if (isDataTableShown()) {
                this.initializeCrossStoreTable(params);
            }
        } else {
            this.proceedToErrorState();
        }

        if (isPublisherView()) {
            showCrossStorePublisherOptions();
            initializeJustStoresInMyContactsCheckboxListener();
            initializeJustOutOfStockStoresCheckboxListener();
        }
    };

    var isPublisherView = function () {
        return $("#crossStoreContainer").attr("data-isPublisherView") === "true";
    };

    var isRetailer = function () {
        return $("#crossStoreContainer").attr("data-isRetailer") === "true";
    };

    var isDataTableShown = function () {
        return isRetailer() || isPublisherView();
    };

    var isLibraryMarket = function (market) {
        return market == 253;
    }

    var showLibraryBranchesIneligibleMessage = function () {
        $("#crossStoreContainer .libraryBranchesIneligibleMessage").removeClass("hidden");
    };
    var hideLibraryBranchesIneligibleMessage = function () {
        $("#crossStoreContainer .libraryBranchesIneligibleMessage").addClass("hidden");
    };

    var showNoStoresMessage = function () {
        $("#crossStoreContainer .noStoresMessage").removeClass("hidden");
    };
    var hideNoStoresMessage = function () {
        $("#crossStoreContainer .noStoresMessage").addClass("hidden");
    };

    var showCrossStorePublisherOptions = function () {
        $("#crossStorePublisherOptions").removeClass("hidden");
    };
    var hideCrossStorePublisherOptions = function () {
        $("#crossStorePublisherOptions").addClass("hidden");
    };

    var showCrossStoreExportOptions = function () {
        $("#crossStoreExportOptions").removeClass("hidden");
    };
    var hideCrossStoreExportOptions = function () {
        $("#crossStoreExportOptions").addClass("hidden");
    };

    var showCrossStoreTable = function () {
        $("#crossStoreTableContainer").removeClass("hidden");
    };
    var hideCrossStoreTable = function () {
        $("#crossStoreTableContainer").addClass("hidden");
    };

    var showTableLoadingAnimation = function () {
        $("#crossStoreContainer .initialLoadingAnimation").removeClass("hidden");
    };
    var hideTableLoadingAnimation = function () {
        $("#crossStoreContainer .initialLoadingAnimation").addClass("hidden");
    };

    var initializeJustStoresInMyContactsCheckboxListener = function () {
        var $justStoresInMyContactsCheckbox = $("#crossStoreOption-justStoresInMyContacts");
        $justStoresInMyContactsCheckbox.on("click", function () {
            _this.reloadCrossStoreViewAfterPublisherViewCheckboxChange();
        });
    };

    var initializeJustOutOfStockStoresCheckboxListener = function () {
        var $justOutOfStockStoresCheckbox = $("#crossStoreOption-justOutOfStockStores");
        $justOutOfStockStoresCheckbox.on("click", function () {
            _this.reloadCrossStoreViewAfterPublisherViewCheckboxChange();
        });
    };

    var getSelectedMarket = function () {
        var $availableMarkets = $("#availableMarketsInCrossStoreView");
        if ($availableMarkets.length && $availableMarkets[0].hasAttribute("val")) {
            return $availableMarkets.attr("val");
        } else {
            return null;
        }
    };

    var getSelectedSku = function () {
        var $crossStoreContainer = $("#crossStoreContainer");
        if ($crossStoreContainer.length && $crossStoreContainer[0].hasAttribute("data-sku")) {
            return $crossStoreContainer.attr("data-sku");
        } else {
            return null;
        }
    };

    var getSelectedTimeFrame = function () {
        var $availableTimeFrames = $("#availableTimeFramesInCrossStoreView");
        if ($availableTimeFrames.length && $availableTimeFrames[0].hasAttribute("val")) {
            return $availableTimeFrames.attr("val");
        } else {
            return null;
        }
    };

    var getSelectedTableDisplayLength = function () {
        var $crossStoreContainer = $("#crossStoreContainer");
        var tableDisplayLengthAttr = "data-selectedTableDisplayLength";
        if ($crossStoreContainer.length > 0 && $crossStoreContainer[0].hasAttribute(tableDisplayLengthAttr)) {
            var tableDisplayLength = parseInt($crossStoreContainer.attr(tableDisplayLengthAttr));
            return isNaN(tableDisplayLength) ? 100 : tableDisplayLength;
        } else {
            return null;
        }
    };

    var getAreJustStoresInMyContactsShown = function () {
        return $("#crossStoreOption-justStoresInMyContacts").is(":checked");
    };

    var getAreJustOutOfStockStoresShown = function () {
        return $("#crossStoreOption-justOutOfStockStores").is(":checked");
    };

    var areParamsValid = function (params) {
        return params.market !== null && params.monthsBack !== null && params.sku !== null
            && params.tableDisplayLength !== null;
    };

    this.getParams = function () {
        var params = {
            market: getSelectedMarket(),
            monthsBack: getSelectedTimeFrame(),
            sku: getSelectedSku(),
            tableDisplayLength: getSelectedTableDisplayLength(),
        };

        if (isPublisherView()) {
            params.areJustStoresInMyContactsShown = getAreJustStoresInMyContactsShown();
            params.areJustOutOfStockStoresShown = getAreJustOutOfStockStoresShown();
        }

        if (!areParamsValid(params)) {
            console.error("There was an error when retrieving the Cross Store View parameters");
            return null;
        } else {
            return params;
        }
    };

    this.reloadCrossStoreViewAfterPublisherViewCheckboxChange = function () {
        var params = this.getParams();
        if (params !== null) {
            this.initializeCrossStoreTable(params);
        } else {
            this.proceedToErrorState();
        }
    };

    this.reloadCrossStoreViewAfterTimeFrameChange = function () {
        var _this = this;
        var params = this.getParams();
        if (params !== null) {
            SaveUserPreferenceAsync("analytics", "selectedTimeFrameInCrossStoreView", params.monthsBack).then(function () {
                if (isDataTableShown()) {
                    _this.initializeCrossStoreTable(params);
                }
                _this.initializePeerData(params);
            }).catch(function () {
                _this.proceedToErrorState();
            });
        } else {
            _this.proceedToErrorState();
        }
    };

    this.reloadCrossStoreViewAfterMarketChange = function () {
        var _this = this;
        var params = this.getParams();
        if (params !== null) {
            SaveUserPreferenceAsync("analytics", "selectedMarketInCrossStoreView", params.market).then(function () {
                if (isDataTableShown()) {
                    _this.initializeCrossStoreTable(params);
                }
                _this.initializePeerData(params);
            }).catch(function () {
                _this.proceedToErrorState();
            });
        } else {
            _this.proceedToErrorState();
        }
    };

    this.proceedToErrorState = function () {
        hideCrossStoreTable();
        hideCrossStoreExportOptions();
        hideCrossStorePublisherOptions();
        $("#crossStoreContainer .crossStoreTableHeader").addClass("hidden");
        $("#crossStoreContainer .crossStoreTableError").removeClass("hidden");
    };

    this.initializePeerData = function (params) {
        var peerDataUrl = "/GetTreelineControl.aspx?controlName=/uc/analytics/PeerData.ascx&" + $.param(params);
        $("#crossStorePeersData").load(peerDataUrl);
    };

    this.initializeCrossStoreTable = function (params) {
        if (isLibraryMarket(params.market)) {
            hideCrossStoreTable();
            hideCrossStoreExportOptions();
            showLibraryBranchesIneligibleMessage();
            return;
        } else {
            hideLibraryBranchesIneligibleMessage();
        }

        showTableLoadingAnimation();
        getCrossStoreRecords(params).then(function (crossStoreRecords) {
            var cumulativeRecord = _.find(crossStoreRecords, function (rec) { return rec.locationId === "ALL1" });
            var tableBodyRecords = _.filter(crossStoreRecords, function (rec) { return rec.locationId !== "ALL1" });

            _this.exportRecords = $.extend(true, [], tableBodyRecords)

            var maxOnHand = getMaxPropertyValue(tableBodyRecords, "onHand");
            var maxOnOrder = getMaxPropertyValue(tableBodyRecords, "onOrder");
            var maxHistoricalSales = getMaxPropertyValue(tableBodyRecords, "historicalSales");
            tableBodyRecords.forEach(function (record) {
                record.onHand = getMetricWithProportionBar(record.onHand, maxOnHand);
                record.onOrder = getMetricWithProportionBar(record.onOrder, maxOnOrder);
                record.historicalSales = getMetricWithProportionBar(record.historicalSales, maxHistoricalSales);
            });

            _this.records = tableBodyRecords;

            $.fn.dataTableExt.oStdClasses.sStripeEven = "wFil tlList even altRow";
            $.fn.dataTableExt.oStdClasses.sStripeOdd = "wFil tlList odd stdRow";
            $("#crossStoreTable").DataTable({
                data: tableBodyRecords,
                columns: [
                    { data: "storeRegionOrName", width: "20em" },
                    { data: "onHand", width: "4em" },
                    { data: "onOrder", width: "4em" },
                    { data: "historicalSales", width: "4em" }
                ],
                destroy: true,
                bAutoWidth: false,
                order: [],
                lengthMenu: [100, 500, 1000],
                initComplete: function () {
                    initializeDisplayLengthChangeListener();
                    populateTableColumnTotals(cumulativeRecord);
                },
                bSortCellsTop: true,
                displayLength: params.tableDisplayLength
            });
            if (crossStoreRecords.length > 0) {
                showCrossStoreTable();
                showCrossStoreExportOptions();
                hideNoStoresMessage();
            } else {
                hideCrossStoreTable();
                hideCrossStoreExportOptions();
                showNoStoresMessage();
            }
        }).catch(function (error) {
            _this.proceedToErrorState();
        }).then(function () { // finally
            hideTableLoadingAnimation();
        });
    }

    function getMetricWithProportionBar(metric, maxMetric) {
        var metricProportion = Math.round((metric / maxMetric) * 100);
        return metric.toString() + "<div class='crossStoreProportionBar' style='width:" + metricProportion + "%'>";
    }

    function getMaxPropertyValue(objectArray, propertyName) {
        return _.max(_.map(objectArray, propertyName));
    }

    function populateTableColumnTotals(cumulativeRecord) {
        $("#crossStoreTable-onHandTotal").html(cumulativeRecord.onHand);
        $("#crossStoreTable-onOrderTotal").html(cumulativeRecord.onOrder);
        $("#crossStoreTable-historicalSalesTotal").html(cumulativeRecord.historicalSales);
    }

    var initializeDisplayLengthChangeListener = function () {
        $("#crossStoreTable").on('length.dt', function (e, settings, len) {
            var onSuccess = function () {
                setSelectedTableDisplayLengthInCrossStoreContainerElement(len);
            };
            var onError = function () {
                console.error("There was an error saving the cross-store table's display length.");
            };
            saveCrossStoreTableDisplayLength(len, onSuccess, onError);
        });
    };

    var setSelectedTableDisplayLengthInCrossStoreContainerElement = function (len) {
        var $crossStoreContainer = $("#crossStoreContainer");
        if ($crossStoreContainer.length > 0) {
            $crossStoreContainer.attr("data-selectedtabledisplaylength", len);
        }
    };

    var saveCrossStoreTableDisplayLength = function (displayLength, onSuccess, onError) {
        SaveUserPreference('analytics', 'selectedCrossStoreTableDisplayLength', displayLength, onSuccess, onError);
    };

    var getCrossStoreRecords = function (params) {
        var rowsPromise = new Promise(function (resolve, reject) {
            $.ajax({
                type: "GET",
                url: "/api/v1/analytics/crossStoreTable",
                data: params,
                success: function (crossStoreRecords) {
                    resolve(crossStoreRecords);
                },
                error: function (err) {
                    reject("There was an error in getting the cross-store table");
                }
            });
        });
        return rowsPromise;
    }
});
;
/**
 * @license
 * Lodash lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE
 */
;(function(){function n(n,t){return n.set(t[0],t[1]),n}function t(n,t){return n.add(t),n}function r(n,t,r){switch(r.length){case 0:return n.call(t);case 1:return n.call(t,r[0]);case 2:return n.call(t,r[0],r[1]);case 3:return n.call(t,r[0],r[1],r[2])}return n.apply(t,r)}function e(n,t,r,e){for(var u=-1,i=null==n?0:n.length;++u<i;){var o=n[u];t(e,o,r(o),n)}return e}function u(n,t){for(var r=-1,e=null==n?0:n.length;++r<e&&false!==t(n[r],r,n););return n}function i(n,t){for(var r=null==n?0:n.length;r--&&false!==t(n[r],r,n););
return n}function o(n,t){for(var r=-1,e=null==n?0:n.length;++r<e;)if(!t(n[r],r,n))return false;return true}function f(n,t){for(var r=-1,e=null==n?0:n.length,u=0,i=[];++r<e;){var o=n[r];t(o,r,n)&&(i[u++]=o)}return i}function c(n,t){return!(null==n||!n.length)&&-1<d(n,t,0)}function a(n,t,r){for(var e=-1,u=null==n?0:n.length;++e<u;)if(r(t,n[e]))return true;return false}function l(n,t){for(var r=-1,e=null==n?0:n.length,u=Array(e);++r<e;)u[r]=t(n[r],r,n);return u}function s(n,t){for(var r=-1,e=t.length,u=n.length;++r<e;)n[u+r]=t[r];
return n}function h(n,t,r,e){var u=-1,i=null==n?0:n.length;for(e&&i&&(r=n[++u]);++u<i;)r=t(r,n[u],u,n);return r}function p(n,t,r,e){var u=null==n?0:n.length;for(e&&u&&(r=n[--u]);u--;)r=t(r,n[u],u,n);return r}function _(n,t){for(var r=-1,e=null==n?0:n.length;++r<e;)if(t(n[r],r,n))return true;return false}function v(n,t,r){var e;return r(n,function(n,r,u){if(t(n,r,u))return e=r,false}),e}function g(n,t,r,e){var u=n.length;for(r+=e?1:-1;e?r--:++r<u;)if(t(n[r],r,n))return r;return-1}function d(n,t,r){if(t===t)n:{
--r;for(var e=n.length;++r<e;)if(n[r]===t){n=r;break n}n=-1}else n=g(n,b,r);return n}function y(n,t,r,e){--r;for(var u=n.length;++r<u;)if(e(n[r],t))return r;return-1}function b(n){return n!==n}function x(n,t){var r=null==n?0:n.length;return r?k(n,t)/r:P}function j(n){return function(t){return null==t?F:t[n]}}function w(n){return function(t){return null==n?F:n[t]}}function m(n,t,r,e,u){return u(n,function(n,u,i){r=e?(e=false,n):t(r,n,u,i)}),r}function A(n,t){var r=n.length;for(n.sort(t);r--;)n[r]=n[r].c;
return n}function k(n,t){for(var r,e=-1,u=n.length;++e<u;){var i=t(n[e]);i!==F&&(r=r===F?i:r+i)}return r}function E(n,t){for(var r=-1,e=Array(n);++r<n;)e[r]=t(r);return e}function O(n,t){return l(t,function(t){return[t,n[t]]})}function S(n){return function(t){return n(t)}}function I(n,t){return l(t,function(t){return n[t]})}function R(n,t){return n.has(t)}function z(n,t){for(var r=-1,e=n.length;++r<e&&-1<d(t,n[r],0););return r}function W(n,t){for(var r=n.length;r--&&-1<d(t,n[r],0););return r}function B(n){
return"\\"+Tn[n]}function L(n){var t=-1,r=Array(n.size);return n.forEach(function(n,e){r[++t]=[e,n]}),r}function U(n,t){return function(r){return n(t(r))}}function C(n,t){for(var r=-1,e=n.length,u=0,i=[];++r<e;){var o=n[r];o!==t&&"__lodash_placeholder__"!==o||(n[r]="__lodash_placeholder__",i[u++]=r)}return i}function D(n){var t=-1,r=Array(n.size);return n.forEach(function(n){r[++t]=n}),r}function M(n){var t=-1,r=Array(n.size);return n.forEach(function(n){r[++t]=[n,n]}),r}function T(n){if(Bn.test(n)){
for(var t=zn.lastIndex=0;zn.test(n);)++t;n=t}else n=tt(n);return n}function $(n){return Bn.test(n)?n.match(zn)||[]:n.split("")}var F,N=1/0,P=NaN,Z=[["ary",128],["bind",1],["bindKey",2],["curry",8],["curryRight",16],["flip",512],["partial",32],["partialRight",64],["rearg",256]],q=/\b__p\+='';/g,V=/\b(__p\+=)''\+/g,K=/(__e\(.*?\)|\b__t\))\+'';/g,G=/&(?:amp|lt|gt|quot|#39);/g,H=/[&<>"']/g,J=RegExp(G.source),Y=RegExp(H.source),Q=/<%-([\s\S]+?)%>/g,X=/<%([\s\S]+?)%>/g,nn=/<%=([\s\S]+?)%>/g,tn=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,rn=/^\w*$/,en=/^\./,un=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,on=/[\\^$.*+?()[\]{}|]/g,fn=RegExp(on.source),cn=/^\s+|\s+$/g,an=/^\s+/,ln=/\s+$/,sn=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,hn=/\{\n\/\* \[wrapped with (.+)\] \*/,pn=/,? & /,_n=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,vn=/\\(\\)?/g,gn=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,dn=/\w*$/,yn=/^[-+]0x[0-9a-f]+$/i,bn=/^0b[01]+$/i,xn=/^\[object .+?Constructor\]$/,jn=/^0o[0-7]+$/i,wn=/^(?:0|[1-9]\d*)$/,mn=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,An=/($^)/,kn=/['\n\r\u2028\u2029\\]/g,En="[\\ufe0e\\ufe0f]?(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?(?:\\u200d(?:[^\\ud800-\\udfff]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff])[\\ufe0e\\ufe0f]?(?:[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|\\ud83c[\\udffb-\\udfff])?)*",On="(?:[\\u2700-\\u27bf]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff])"+En,Sn="(?:[^\\ud800-\\udfff][\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]?|[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff]|[\\ud800-\\udfff])",In=RegExp("['\u2019]","g"),Rn=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff]","g"),zn=RegExp("\\ud83c[\\udffb-\\udfff](?=\\ud83c[\\udffb-\\udfff])|"+Sn+En,"g"),Wn=RegExp(["[A-Z\\xc0-\\xd6\\xd8-\\xde]?[a-z\\xdf-\\xf6\\xf8-\\xff]+(?:['\u2019](?:d|ll|m|re|s|t|ve))?(?=[\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000]|[A-Z\\xc0-\\xd6\\xd8-\\xde]|$)|(?:[A-Z\\xc0-\\xd6\\xd8-\\xde]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])+(?:['\u2019](?:D|LL|M|RE|S|T|VE))?(?=[\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000]|[A-Z\\xc0-\\xd6\\xd8-\\xde](?:[a-z\\xdf-\\xf6\\xf8-\\xff]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])|$)|[A-Z\\xc0-\\xd6\\xd8-\\xde]?(?:[a-z\\xdf-\\xf6\\xf8-\\xff]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])+(?:['\u2019](?:d|ll|m|re|s|t|ve))?|[A-Z\\xc0-\\xd6\\xd8-\\xde]+(?:['\u2019](?:D|LL|M|RE|S|T|VE))?|\\d*(?:(?:1ST|2ND|3RD|(?![123])\\dTH)\\b)|\\d*(?:(?:1st|2nd|3rd|(?![123])\\dth)\\b)|\\d+",On].join("|"),"g"),Bn=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\ufe0e\\ufe0f]"),Ln=/[a-z][A-Z]|[A-Z]{2,}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,Un="Array Buffer DataView Date Error Float32Array Float64Array Function Int8Array Int16Array Int32Array Map Math Object Promise RegExp Set String Symbol TypeError Uint8Array Uint8ClampedArray Uint16Array Uint32Array WeakMap _ clearTimeout isFinite parseInt setTimeout".split(" "),Cn={};
Cn["[object Float32Array]"]=Cn["[object Float64Array]"]=Cn["[object Int8Array]"]=Cn["[object Int16Array]"]=Cn["[object Int32Array]"]=Cn["[object Uint8Array]"]=Cn["[object Uint8ClampedArray]"]=Cn["[object Uint16Array]"]=Cn["[object Uint32Array]"]=true,Cn["[object Arguments]"]=Cn["[object Array]"]=Cn["[object ArrayBuffer]"]=Cn["[object Boolean]"]=Cn["[object DataView]"]=Cn["[object Date]"]=Cn["[object Error]"]=Cn["[object Function]"]=Cn["[object Map]"]=Cn["[object Number]"]=Cn["[object Object]"]=Cn["[object RegExp]"]=Cn["[object Set]"]=Cn["[object String]"]=Cn["[object WeakMap]"]=false;
var Dn={};Dn["[object Arguments]"]=Dn["[object Array]"]=Dn["[object ArrayBuffer]"]=Dn["[object DataView]"]=Dn["[object Boolean]"]=Dn["[object Date]"]=Dn["[object Float32Array]"]=Dn["[object Float64Array]"]=Dn["[object Int8Array]"]=Dn["[object Int16Array]"]=Dn["[object Int32Array]"]=Dn["[object Map]"]=Dn["[object Number]"]=Dn["[object Object]"]=Dn["[object RegExp]"]=Dn["[object Set]"]=Dn["[object String]"]=Dn["[object Symbol]"]=Dn["[object Uint8Array]"]=Dn["[object Uint8ClampedArray]"]=Dn["[object Uint16Array]"]=Dn["[object Uint32Array]"]=true,
Dn["[object Error]"]=Dn["[object Function]"]=Dn["[object WeakMap]"]=false;var Mn,Tn={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},$n=parseFloat,Fn=parseInt,Nn=typeof global=="object"&&global&&global.Object===Object&&global,Pn=typeof self=="object"&&self&&self.Object===Object&&self,Zn=Nn||Pn||Function("return this")(),qn=typeof exports=="object"&&exports&&!exports.nodeType&&exports,Vn=qn&&typeof module=="object"&&module&&!module.nodeType&&module,Kn=Vn&&Vn.exports===qn,Gn=Kn&&Nn.process;
n:{try{Mn=Gn&&Gn.binding&&Gn.binding("util");break n}catch(n){}Mn=void 0}var Hn=Mn&&Mn.isArrayBuffer,Jn=Mn&&Mn.isDate,Yn=Mn&&Mn.isMap,Qn=Mn&&Mn.isRegExp,Xn=Mn&&Mn.isSet,nt=Mn&&Mn.isTypedArray,tt=j("length"),rt=w({"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I",
"\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O","\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss","\u0100":"A","\u0102":"A","\u0104":"A","\u0101":"a","\u0103":"a","\u0105":"a","\u0106":"C","\u0108":"C","\u010a":"C",
"\u010c":"C","\u0107":"c","\u0109":"c","\u010b":"c","\u010d":"c","\u010e":"D","\u0110":"D","\u010f":"d","\u0111":"d","\u0112":"E","\u0114":"E","\u0116":"E","\u0118":"E","\u011a":"E","\u0113":"e","\u0115":"e","\u0117":"e","\u0119":"e","\u011b":"e","\u011c":"G","\u011e":"G","\u0120":"G","\u0122":"G","\u011d":"g","\u011f":"g","\u0121":"g","\u0123":"g","\u0124":"H","\u0126":"H","\u0125":"h","\u0127":"h","\u0128":"I","\u012a":"I","\u012c":"I","\u012e":"I","\u0130":"I","\u0129":"i","\u012b":"i","\u012d":"i",
"\u012f":"i","\u0131":"i","\u0134":"J","\u0135":"j","\u0136":"K","\u0137":"k","\u0138":"k","\u0139":"L","\u013b":"L","\u013d":"L","\u013f":"L","\u0141":"L","\u013a":"l","\u013c":"l","\u013e":"l","\u0140":"l","\u0142":"l","\u0143":"N","\u0145":"N","\u0147":"N","\u014a":"N","\u0144":"n","\u0146":"n","\u0148":"n","\u014b":"n","\u014c":"O","\u014e":"O","\u0150":"O","\u014d":"o","\u014f":"o","\u0151":"o","\u0154":"R","\u0156":"R","\u0158":"R","\u0155":"r","\u0157":"r","\u0159":"r","\u015a":"S","\u015c":"S",
"\u015e":"S","\u0160":"S","\u015b":"s","\u015d":"s","\u015f":"s","\u0161":"s","\u0162":"T","\u0164":"T","\u0166":"T","\u0163":"t","\u0165":"t","\u0167":"t","\u0168":"U","\u016a":"U","\u016c":"U","\u016e":"U","\u0170":"U","\u0172":"U","\u0169":"u","\u016b":"u","\u016d":"u","\u016f":"u","\u0171":"u","\u0173":"u","\u0174":"W","\u0175":"w","\u0176":"Y","\u0177":"y","\u0178":"Y","\u0179":"Z","\u017b":"Z","\u017d":"Z","\u017a":"z","\u017c":"z","\u017e":"z","\u0132":"IJ","\u0133":"ij","\u0152":"Oe","\u0153":"oe",
"\u0149":"'n","\u017f":"s"}),et=w({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"}),ut=w({"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&#39;":"'"}),it=function w(En){function On(n){if(xu(n)&&!af(n)&&!(n instanceof Mn)){if(n instanceof zn)return n;if(ci.call(n,"__wrapped__"))return Pe(n)}return new zn(n)}function Sn(){}function zn(n,t){this.__wrapped__=n,this.__actions__=[],this.__chain__=!!t,this.__index__=0,this.__values__=F}function Mn(n){this.__wrapped__=n,this.__actions__=[],this.__dir__=1,
this.__filtered__=false,this.__iteratees__=[],this.__takeCount__=4294967295,this.__views__=[]}function Tn(n){var t=-1,r=null==n?0:n.length;for(this.clear();++t<r;){var e=n[t];this.set(e[0],e[1])}}function Nn(n){var t=-1,r=null==n?0:n.length;for(this.clear();++t<r;){var e=n[t];this.set(e[0],e[1])}}function Pn(n){var t=-1,r=null==n?0:n.length;for(this.clear();++t<r;){var e=n[t];this.set(e[0],e[1])}}function qn(n){var t=-1,r=null==n?0:n.length;for(this.__data__=new Pn;++t<r;)this.add(n[t])}function Vn(n){
this.size=(this.__data__=new Nn(n)).size}function Gn(n,t){var r,e=af(n),u=!e&&cf(n),i=!e&&!u&&sf(n),o=!e&&!u&&!i&&gf(n),u=(e=e||u||i||o)?E(n.length,ri):[],f=u.length;for(r in n)!t&&!ci.call(n,r)||e&&("length"==r||i&&("offset"==r||"parent"==r)||o&&("buffer"==r||"byteLength"==r||"byteOffset"==r)||Re(r,f))||u.push(r);return u}function tt(n){var t=n.length;return t?n[cr(0,t-1)]:F}function ot(n,t){return Te(Mr(n),gt(t,0,n.length))}function ft(n){return Te(Mr(n))}function ct(n,t,r){(r===F||hu(n[t],r))&&(r!==F||t in n)||_t(n,t,r);
}function at(n,t,r){var e=n[t];ci.call(n,t)&&hu(e,r)&&(r!==F||t in n)||_t(n,t,r)}function lt(n,t){for(var r=n.length;r--;)if(hu(n[r][0],t))return r;return-1}function st(n,t,r,e){return oo(n,function(n,u,i){t(e,n,r(n),i)}),e}function ht(n,t){return n&&Tr(t,Lu(t),n)}function pt(n,t){return n&&Tr(t,Uu(t),n)}function _t(n,t,r){"__proto__"==t&&Ei?Ei(n,t,{configurable:true,enumerable:true,value:r,writable:true}):n[t]=r}function vt(n,t){for(var r=-1,e=t.length,u=Hu(e),i=null==n;++r<e;)u[r]=i?F:Wu(n,t[r]);return u;
}function gt(n,t,r){return n===n&&(r!==F&&(n=n<=r?n:r),t!==F&&(n=n>=t?n:t)),n}function dt(n,t,r,e,i,o){var f,c=1&t,a=2&t,l=4&t;if(r&&(f=i?r(n,e,i,o):r(n)),f!==F)return f;if(!bu(n))return n;if(e=af(n)){if(f=Ee(n),!c)return Mr(n,f)}else{var s=yo(n),h="[object Function]"==s||"[object GeneratorFunction]"==s;if(sf(n))return Wr(n,c);if("[object Object]"==s||"[object Arguments]"==s||h&&!i){if(f=a||h?{}:Oe(n),!c)return a?Fr(n,pt(f,n)):$r(n,ht(f,n))}else{if(!Dn[s])return i?n:{};f=Se(n,s,dt,c)}}if(o||(o=new Vn),
i=o.get(n))return i;o.set(n,f);var a=l?a?ye:de:a?Uu:Lu,p=e?F:a(n);return u(p||n,function(e,u){p&&(u=e,e=n[u]),at(f,u,dt(e,t,r,u,n,o))}),f}function yt(n){var t=Lu(n);return function(r){return bt(r,n,t)}}function bt(n,t,r){var e=r.length;if(null==n)return!e;for(n=ni(n);e--;){var u=r[e],i=t[u],o=n[u];if(o===F&&!(u in n)||!i(o))return false}return true}function xt(n,t,r){if(typeof n!="function")throw new ei("Expected a function");return jo(function(){n.apply(F,r)},t)}function jt(n,t,r,e){var u=-1,i=c,o=true,f=n.length,s=[],h=t.length;
if(!f)return s;r&&(t=l(t,S(r))),e?(i=a,o=false):200<=t.length&&(i=R,o=false,t=new qn(t));n:for(;++u<f;){var p=n[u],_=null==r?p:r(p),p=e||0!==p?p:0;if(o&&_===_){for(var v=h;v--;)if(t[v]===_)continue n;s.push(p)}else i(t,_,e)||s.push(p)}return s}function wt(n,t){var r=true;return oo(n,function(n,e,u){return r=!!t(n,e,u)}),r}function mt(n,t,r){for(var e=-1,u=n.length;++e<u;){var i=n[e],o=t(i);if(null!=o&&(f===F?o===o&&!Au(o):r(o,f)))var f=o,c=i}return c}function At(n,t){var r=[];return oo(n,function(n,e,u){
t(n,e,u)&&r.push(n)}),r}function kt(n,t,r,e,u){var i=-1,o=n.length;for(r||(r=Ie),u||(u=[]);++i<o;){var f=n[i];0<t&&r(f)?1<t?kt(f,t-1,r,e,u):s(u,f):e||(u[u.length]=f)}return u}function Et(n,t){return n&&co(n,t,Lu)}function Ot(n,t){return n&&ao(n,t,Lu)}function St(n,t){return f(t,function(t){return gu(n[t])})}function It(n,t){t=Rr(t,n);for(var r=0,e=t.length;null!=n&&r<e;)n=n[$e(t[r++])];return r&&r==e?n:F}function Rt(n,t,r){return t=t(n),af(n)?t:s(t,r(n))}function zt(n){if(null==n)n=n===F?"[object Undefined]":"[object Null]";else if(ki&&ki in ni(n)){
var t=ci.call(n,ki),r=n[ki];try{n[ki]=F;var e=true}catch(n){}var u=si.call(n);e&&(t?n[ki]=r:delete n[ki]),n=u}else n=si.call(n);return n}function Wt(n,t){return n>t}function Bt(n,t){return null!=n&&ci.call(n,t)}function Lt(n,t){return null!=n&&t in ni(n)}function Ut(n,t,r){for(var e=r?a:c,u=n[0].length,i=n.length,o=i,f=Hu(i),s=1/0,h=[];o--;){var p=n[o];o&&t&&(p=l(p,S(t))),s=Mi(p.length,s),f[o]=!r&&(t||120<=u&&120<=p.length)?new qn(o&&p):F}var p=n[0],_=-1,v=f[0];n:for(;++_<u&&h.length<s;){var g=p[_],d=t?t(g):g,g=r||0!==g?g:0;
if(v?!R(v,d):!e(h,d,r)){for(o=i;--o;){var y=f[o];if(y?!R(y,d):!e(n[o],d,r))continue n}v&&v.push(d),h.push(g)}}return h}function Ct(n,t,r){var e={};return Et(n,function(n,u,i){t(e,r(n),u,i)}),e}function Dt(n,t,e){return t=Rr(t,n),n=2>t.length?n:It(n,vr(t,0,-1)),t=null==n?n:n[$e(Ge(t))],null==t?F:r(t,n,e)}function Mt(n){return xu(n)&&"[object Arguments]"==zt(n)}function Tt(n){return xu(n)&&"[object ArrayBuffer]"==zt(n)}function $t(n){return xu(n)&&"[object Date]"==zt(n)}function Ft(n,t,r,e,u){if(n===t)t=true;else if(null==n||null==t||!xu(n)&&!xu(t))t=n!==n&&t!==t;else n:{
var i=af(n),o=af(t),f=i?"[object Array]":yo(n),c=o?"[object Array]":yo(t),f="[object Arguments]"==f?"[object Object]":f,c="[object Arguments]"==c?"[object Object]":c,a="[object Object]"==f,o="[object Object]"==c;if((c=f==c)&&sf(n)){if(!sf(t)){t=false;break n}i=true,a=false}if(c&&!a)u||(u=new Vn),t=i||gf(n)?_e(n,t,r,e,Ft,u):ve(n,t,f,r,e,Ft,u);else{if(!(1&r)&&(i=a&&ci.call(n,"__wrapped__"),f=o&&ci.call(t,"__wrapped__"),i||f)){n=i?n.value():n,t=f?t.value():t,u||(u=new Vn),t=Ft(n,t,r,e,u);break n}if(c)t:if(u||(u=new Vn),
i=1&r,f=de(n),o=f.length,c=de(t).length,o==c||i){for(a=o;a--;){var l=f[a];if(!(i?l in t:ci.call(t,l))){t=false;break t}}if((c=u.get(n))&&u.get(t))t=c==t;else{c=true,u.set(n,t),u.set(t,n);for(var s=i;++a<o;){var l=f[a],h=n[l],p=t[l];if(e)var _=i?e(p,h,l,t,n,u):e(h,p,l,n,t,u);if(_===F?h!==p&&!Ft(h,p,r,e,u):!_){c=false;break}s||(s="constructor"==l)}c&&!s&&(r=n.constructor,e=t.constructor,r!=e&&"constructor"in n&&"constructor"in t&&!(typeof r=="function"&&r instanceof r&&typeof e=="function"&&e instanceof e)&&(c=false)),
u.delete(n),u.delete(t),t=c}}else t=false;else t=false}}return t}function Nt(n){return xu(n)&&"[object Map]"==yo(n)}function Pt(n,t,r,e){var u=r.length,i=u,o=!e;if(null==n)return!i;for(n=ni(n);u--;){var f=r[u];if(o&&f[2]?f[1]!==n[f[0]]:!(f[0]in n))return false}for(;++u<i;){var f=r[u],c=f[0],a=n[c],l=f[1];if(o&&f[2]){if(a===F&&!(c in n))return false}else{if(f=new Vn,e)var s=e(a,l,c,n,t,f);if(s===F?!Ft(l,a,3,e,f):!s)return false}}return true}function Zt(n){return!(!bu(n)||li&&li in n)&&(gu(n)?_i:xn).test(Fe(n))}function qt(n){
return xu(n)&&"[object RegExp]"==zt(n)}function Vt(n){return xu(n)&&"[object Set]"==yo(n)}function Kt(n){return xu(n)&&yu(n.length)&&!!Cn[zt(n)]}function Gt(n){return typeof n=="function"?n:null==n?Nu:typeof n=="object"?af(n)?Xt(n[0],n[1]):Qt(n):Vu(n)}function Ht(n){if(!Le(n))return Ci(n);var t,r=[];for(t in ni(n))ci.call(n,t)&&"constructor"!=t&&r.push(t);return r}function Jt(n,t){return n<t}function Yt(n,t){var r=-1,e=pu(n)?Hu(n.length):[];return oo(n,function(n,u,i){e[++r]=t(n,u,i)}),e}function Qt(n){
var t=me(n);return 1==t.length&&t[0][2]?Ue(t[0][0],t[0][1]):function(r){return r===n||Pt(r,n,t)}}function Xt(n,t){return We(n)&&t===t&&!bu(t)?Ue($e(n),t):function(r){var e=Wu(r,n);return e===F&&e===t?Bu(r,n):Ft(t,e,3)}}function nr(n,t,r,e,u){n!==t&&co(t,function(i,o){if(bu(i)){u||(u=new Vn);var f=u,c=n[o],a=t[o],l=f.get(a);if(l)ct(n,o,l);else{var l=e?e(c,a,o+"",n,t,f):F,s=l===F;if(s){var h=af(a),p=!h&&sf(a),_=!h&&!p&&gf(a),l=a;h||p||_?af(c)?l=c:_u(c)?l=Mr(c):p?(s=false,l=Wr(a,true)):_?(s=false,l=Lr(a,true)):l=[]:wu(a)||cf(a)?(l=c,
cf(c)?l=Ru(c):(!bu(c)||r&&gu(c))&&(l=Oe(a))):s=false}s&&(f.set(a,l),nr(l,a,r,e,f),f.delete(a)),ct(n,o,l)}}else f=e?e(n[o],i,o+"",n,t,u):F,f===F&&(f=i),ct(n,o,f)},Uu)}function tr(n,t){var r=n.length;if(r)return t+=0>t?r:0,Re(t,r)?n[t]:F}function rr(n,t,r){var e=-1;return t=l(t.length?t:[Nu],S(je())),n=Yt(n,function(n){return{a:l(t,function(t){return t(n)}),b:++e,c:n}}),A(n,function(n,t){var e;n:{e=-1;for(var u=n.a,i=t.a,o=u.length,f=r.length;++e<o;){var c=Ur(u[e],i[e]);if(c){e=e>=f?c:c*("desc"==r[e]?-1:1);
break n}}e=n.b-t.b}return e})}function er(n,t){return ur(n,t,function(t,r){return Bu(n,r)})}function ur(n,t,r){for(var e=-1,u=t.length,i={};++e<u;){var o=t[e],f=It(n,o);r(f,o)&&pr(i,Rr(o,n),f)}return i}function ir(n){return function(t){return It(t,n)}}function or(n,t,r,e){var u=e?y:d,i=-1,o=t.length,f=n;for(n===t&&(t=Mr(t)),r&&(f=l(n,S(r)));++i<o;)for(var c=0,a=t[i],a=r?r(a):a;-1<(c=u(f,a,c,e));)f!==n&&wi.call(f,c,1),wi.call(n,c,1);return n}function fr(n,t){for(var r=n?t.length:0,e=r-1;r--;){var u=t[r];
if(r==e||u!==i){var i=u;Re(u)?wi.call(n,u,1):mr(n,u)}}}function cr(n,t){return n+zi(Fi()*(t-n+1))}function ar(n,t){var r="";if(!n||1>t||9007199254740991<t)return r;do t%2&&(r+=n),(t=zi(t/2))&&(n+=n);while(t);return r}function lr(n,t){return wo(Ce(n,t,Nu),n+"")}function sr(n){return tt(Du(n))}function hr(n,t){var r=Du(n);return Te(r,gt(t,0,r.length))}function pr(n,t,r,e){if(!bu(n))return n;t=Rr(t,n);for(var u=-1,i=t.length,o=i-1,f=n;null!=f&&++u<i;){var c=$e(t[u]),a=r;if(u!=o){var l=f[c],a=e?e(l,c,f):F;
a===F&&(a=bu(l)?l:Re(t[u+1])?[]:{})}at(f,c,a),f=f[c]}return n}function _r(n){return Te(Du(n))}function vr(n,t,r){var e=-1,u=n.length;for(0>t&&(t=-t>u?0:u+t),r=r>u?u:r,0>r&&(r+=u),u=t>r?0:r-t>>>0,t>>>=0,r=Hu(u);++e<u;)r[e]=n[e+t];return r}function gr(n,t){var r;return oo(n,function(n,e,u){return r=t(n,e,u),!r}),!!r}function dr(n,t,r){var e=0,u=null==n?e:n.length;if(typeof t=="number"&&t===t&&2147483647>=u){for(;e<u;){var i=e+u>>>1,o=n[i];null!==o&&!Au(o)&&(r?o<=t:o<t)?e=i+1:u=i}return u}return yr(n,t,Nu,r);
}function yr(n,t,r,e){t=r(t);for(var u=0,i=null==n?0:n.length,o=t!==t,f=null===t,c=Au(t),a=t===F;u<i;){var l=zi((u+i)/2),s=r(n[l]),h=s!==F,p=null===s,_=s===s,v=Au(s);(o?e||_:a?_&&(e||h):f?_&&h&&(e||!p):c?_&&h&&!p&&(e||!v):p||v?0:e?s<=t:s<t)?u=l+1:i=l}return Mi(i,4294967294)}function br(n,t){for(var r=-1,e=n.length,u=0,i=[];++r<e;){var o=n[r],f=t?t(o):o;if(!r||!hu(f,c)){var c=f;i[u++]=0===o?0:o}}return i}function xr(n){return typeof n=="number"?n:Au(n)?P:+n}function jr(n){if(typeof n=="string")return n;
if(af(n))return l(n,jr)+"";if(Au(n))return uo?uo.call(n):"";var t=n+"";return"0"==t&&1/n==-N?"-0":t}function wr(n,t,r){var e=-1,u=c,i=n.length,o=true,f=[],l=f;if(r)o=false,u=a;else if(200<=i){if(u=t?null:po(n))return D(u);o=false,u=R,l=new qn}else l=t?[]:f;n:for(;++e<i;){var s=n[e],h=t?t(s):s,s=r||0!==s?s:0;if(o&&h===h){for(var p=l.length;p--;)if(l[p]===h)continue n;t&&l.push(h),f.push(s)}else u(l,h,r)||(l!==f&&l.push(h),f.push(s))}return f}function mr(n,t){return t=Rr(t,n),n=2>t.length?n:It(n,vr(t,0,-1)),
null==n||delete n[$e(Ge(t))]}function Ar(n,t,r,e){for(var u=n.length,i=e?u:-1;(e?i--:++i<u)&&t(n[i],i,n););return r?vr(n,e?0:i,e?i+1:u):vr(n,e?i+1:0,e?u:i)}function kr(n,t){var r=n;return r instanceof Mn&&(r=r.value()),h(t,function(n,t){return t.func.apply(t.thisArg,s([n],t.args))},r)}function Er(n,t,r){var e=n.length;if(2>e)return e?wr(n[0]):[];for(var u=-1,i=Hu(e);++u<e;)for(var o=n[u],f=-1;++f<e;)f!=u&&(i[u]=jt(i[u]||o,n[f],t,r));return wr(kt(i,1),t,r)}function Or(n,t,r){for(var e=-1,u=n.length,i=t.length,o={};++e<u;)r(o,n[e],e<i?t[e]:F);
return o}function Sr(n){return _u(n)?n:[]}function Ir(n){return typeof n=="function"?n:Nu}function Rr(n,t){return af(n)?n:We(n,t)?[n]:mo(zu(n))}function zr(n,t,r){var e=n.length;return r=r===F?e:r,!t&&r>=e?n:vr(n,t,r)}function Wr(n,t){if(t)return n.slice();var r=n.length,r=yi?yi(r):new n.constructor(r);return n.copy(r),r}function Br(n){var t=new n.constructor(n.byteLength);return new di(t).set(new di(n)),t}function Lr(n,t){return new n.constructor(t?Br(n.buffer):n.buffer,n.byteOffset,n.length)}function Ur(n,t){
if(n!==t){var r=n!==F,e=null===n,u=n===n,i=Au(n),o=t!==F,f=null===t,c=t===t,a=Au(t);if(!f&&!a&&!i&&n>t||i&&o&&c&&!f&&!a||e&&o&&c||!r&&c||!u)return 1;if(!e&&!i&&!a&&n<t||a&&r&&u&&!e&&!i||f&&r&&u||!o&&u||!c)return-1}return 0}function Cr(n,t,r,e){var u=-1,i=n.length,o=r.length,f=-1,c=t.length,a=Di(i-o,0),l=Hu(c+a);for(e=!e;++f<c;)l[f]=t[f];for(;++u<o;)(e||u<i)&&(l[r[u]]=n[u]);for(;a--;)l[f++]=n[u++];return l}function Dr(n,t,r,e){var u=-1,i=n.length,o=-1,f=r.length,c=-1,a=t.length,l=Di(i-f,0),s=Hu(l+a);
for(e=!e;++u<l;)s[u]=n[u];for(l=u;++c<a;)s[l+c]=t[c];for(;++o<f;)(e||u<i)&&(s[l+r[o]]=n[u++]);return s}function Mr(n,t){var r=-1,e=n.length;for(t||(t=Hu(e));++r<e;)t[r]=n[r];return t}function Tr(n,t,r,e){var u=!r;r||(r={});for(var i=-1,o=t.length;++i<o;){var f=t[i],c=e?e(r[f],n[f],f,r,n):F;c===F&&(c=n[f]),u?_t(r,f,c):at(r,f,c)}return r}function $r(n,t){return Tr(n,vo(n),t)}function Fr(n,t){return Tr(n,go(n),t)}function Nr(n,t){return function(r,u){var i=af(r)?e:st,o=t?t():{};return i(r,n,je(u,2),o);
}}function Pr(n){return lr(function(t,r){var e=-1,u=r.length,i=1<u?r[u-1]:F,o=2<u?r[2]:F,i=3<n.length&&typeof i=="function"?(u--,i):F;for(o&&ze(r[0],r[1],o)&&(i=3>u?F:i,u=1),t=ni(t);++e<u;)(o=r[e])&&n(t,o,e,i);return t})}function Zr(n,t){return function(r,e){if(null==r)return r;if(!pu(r))return n(r,e);for(var u=r.length,i=t?u:-1,o=ni(r);(t?i--:++i<u)&&false!==e(o[i],i,o););return r}}function qr(n){return function(t,r,e){var u=-1,i=ni(t);e=e(t);for(var o=e.length;o--;){var f=e[n?o:++u];if(false===r(i[f],f,i))break;
}return t}}function Vr(n,t,r){function e(){return(this&&this!==Zn&&this instanceof e?i:n).apply(u?r:this,arguments)}var u=1&t,i=Hr(n);return e}function Kr(n){return function(t){t=zu(t);var r=Bn.test(t)?$(t):F,e=r?r[0]:t.charAt(0);return t=r?zr(r,1).join(""):t.slice(1),e[n]()+t}}function Gr(n){return function(t){return h($u(Tu(t).replace(In,"")),n,"")}}function Hr(n){return function(){var t=arguments;switch(t.length){case 0:return new n;case 1:return new n(t[0]);case 2:return new n(t[0],t[1]);case 3:
return new n(t[0],t[1],t[2]);case 4:return new n(t[0],t[1],t[2],t[3]);case 5:return new n(t[0],t[1],t[2],t[3],t[4]);case 6:return new n(t[0],t[1],t[2],t[3],t[4],t[5]);case 7:return new n(t[0],t[1],t[2],t[3],t[4],t[5],t[6])}var r=io(n.prototype),t=n.apply(r,t);return bu(t)?t:r}}function Jr(n,t,e){function u(){for(var o=arguments.length,f=Hu(o),c=o,a=xe(u);c--;)f[c]=arguments[c];return c=3>o&&f[0]!==a&&f[o-1]!==a?[]:C(f,a),o-=c.length,o<e?fe(n,t,Xr,u.placeholder,F,f,c,F,F,e-o):r(this&&this!==Zn&&this instanceof u?i:n,this,f);
}var i=Hr(n);return u}function Yr(n){return function(t,r,e){var u=ni(t);if(!pu(t)){var i=je(r,3);t=Lu(t),r=function(n){return i(u[n],n,u)}}return r=n(t,r,e),-1<r?u[i?t[r]:r]:F}}function Qr(n){return ge(function(t){var r=t.length,e=r,u=zn.prototype.thru;for(n&&t.reverse();e--;){var i=t[e];if(typeof i!="function")throw new ei("Expected a function");if(u&&!o&&"wrapper"==be(i))var o=new zn([],true)}for(e=o?e:r;++e<r;)var i=t[e],u=be(i),f="wrapper"==u?_o(i):F,o=f&&Be(f[0])&&424==f[1]&&!f[4].length&&1==f[9]?o[be(f[0])].apply(o,f[3]):1==i.length&&Be(i)?o[u]():o.thru(i);
return function(){var n=arguments,e=n[0];if(o&&1==n.length&&af(e))return o.plant(e).value();for(var u=0,n=r?t[u].apply(this,n):e;++u<r;)n=t[u].call(this,n);return n}})}function Xr(n,t,r,e,u,i,o,f,c,a){function l(){for(var d=arguments.length,y=Hu(d),b=d;b--;)y[b]=arguments[b];if(_){var x,j=xe(l),b=y.length;for(x=0;b--;)y[b]===j&&++x}if(e&&(y=Cr(y,e,u,_)),i&&(y=Dr(y,i,o,_)),d-=x,_&&d<a)return j=C(y,j),fe(n,t,Xr,l.placeholder,r,y,j,f,c,a-d);if(j=h?r:this,b=p?j[n]:n,d=y.length,f){x=y.length;for(var w=Mi(f.length,x),m=Mr(y);w--;){
var A=f[w];y[w]=Re(A,x)?m[A]:F}}else v&&1<d&&y.reverse();return s&&c<d&&(y.length=c),this&&this!==Zn&&this instanceof l&&(b=g||Hr(b)),b.apply(j,y)}var s=128&t,h=1&t,p=2&t,_=24&t,v=512&t,g=p?F:Hr(n);return l}function ne(n,t){return function(r,e){return Ct(r,n,t(e))}}function te(n,t){return function(r,e){var u;if(r===F&&e===F)return t;if(r!==F&&(u=r),e!==F){if(u===F)return e;typeof r=="string"||typeof e=="string"?(r=jr(r),e=jr(e)):(r=xr(r),e=xr(e)),u=n(r,e)}return u}}function re(n){return ge(function(t){
return t=l(t,S(je())),lr(function(e){var u=this;return n(t,function(n){return r(n,u,e)})})})}function ee(n,t){t=t===F?" ":jr(t);var r=t.length;return 2>r?r?ar(t,n):t:(r=ar(t,Ri(n/T(t))),Bn.test(t)?zr($(r),0,n).join(""):r.slice(0,n))}function ue(n,t,e,u){function i(){for(var t=-1,c=arguments.length,a=-1,l=u.length,s=Hu(l+c),h=this&&this!==Zn&&this instanceof i?f:n;++a<l;)s[a]=u[a];for(;c--;)s[a++]=arguments[++t];return r(h,o?e:this,s)}var o=1&t,f=Hr(n);return i}function ie(n){return function(t,r,e){
e&&typeof e!="number"&&ze(t,r,e)&&(r=e=F),t=Eu(t),r===F?(r=t,t=0):r=Eu(r),e=e===F?t<r?1:-1:Eu(e);var u=-1;r=Di(Ri((r-t)/(e||1)),0);for(var i=Hu(r);r--;)i[n?r:++u]=t,t+=e;return i}}function oe(n){return function(t,r){return typeof t=="string"&&typeof r=="string"||(t=Iu(t),r=Iu(r)),n(t,r)}}function fe(n,t,r,e,u,i,o,f,c,a){var l=8&t,s=l?o:F;o=l?F:o;var h=l?i:F;return i=l?F:i,t=(t|(l?32:64))&~(l?64:32),4&t||(t&=-4),u=[n,t,u,h,s,i,o,f,c,a],r=r.apply(F,u),Be(n)&&xo(r,u),r.placeholder=e,De(r,n,t)}function ce(n){
var t=Xu[n];return function(n,r){if(n=Iu(n),r=null==r?0:Mi(Ou(r),292)){var e=(zu(n)+"e").split("e"),e=t(e[0]+"e"+(+e[1]+r)),e=(zu(e)+"e").split("e");return+(e[0]+"e"+(+e[1]-r))}return t(n)}}function ae(n){return function(t){var r=yo(t);return"[object Map]"==r?L(t):"[object Set]"==r?M(t):O(t,n(t))}}function le(n,t,r,e,u,i,o,f){var c=2&t;if(!c&&typeof n!="function")throw new ei("Expected a function");var a=e?e.length:0;if(a||(t&=-97,e=u=F),o=o===F?o:Di(Ou(o),0),f=f===F?f:Ou(f),a-=u?u.length:0,64&t){
var l=e,s=u;e=u=F}var h=c?F:_o(n);return i=[n,t,r,e,u,l,s,i,o,f],h&&(r=i[1],n=h[1],t=r|n,e=128==n&&8==r||128==n&&256==r&&i[7].length<=h[8]||384==n&&h[7].length<=h[8]&&8==r,131>t||e)&&(1&n&&(i[2]=h[2],t|=1&r?0:4),(r=h[3])&&(e=i[3],i[3]=e?Cr(e,r,h[4]):r,i[4]=e?C(i[3],"__lodash_placeholder__"):h[4]),(r=h[5])&&(e=i[5],i[5]=e?Dr(e,r,h[6]):r,i[6]=e?C(i[5],"__lodash_placeholder__"):h[6]),(r=h[7])&&(i[7]=r),128&n&&(i[8]=null==i[8]?h[8]:Mi(i[8],h[8])),null==i[9]&&(i[9]=h[9]),i[0]=h[0],i[1]=t),n=i[0],t=i[1],
r=i[2],e=i[3],u=i[4],f=i[9]=i[9]===F?c?0:n.length:Di(i[9]-a,0),!f&&24&t&&(t&=-25),De((h?lo:xo)(t&&1!=t?8==t||16==t?Jr(n,t,f):32!=t&&33!=t||u.length?Xr.apply(F,i):ue(n,t,r,e):Vr(n,t,r),i),n,t)}function se(n,t,r,e){return n===F||hu(n,ii[r])&&!ci.call(e,r)?t:n}function he(n,t,r,e,u,i){return bu(n)&&bu(t)&&(i.set(t,n),nr(n,t,F,he,i),i.delete(t)),n}function pe(n,t){return t!==F&&wu(n)?F:n}function _e(n,t,r,e,u,i){var o=1&r,f=n.length,c=t.length;if(f!=c&&!(o&&c>f))return false;if((c=i.get(n))&&i.get(t))return c==t;
var c=-1,a=true,l=2&r?new qn:F;for(i.set(n,t),i.set(t,n);++c<f;){var s=n[c],h=t[c];if(e)var p=o?e(h,s,c,t,n,i):e(s,h,c,n,t,i);if(p!==F){if(p)continue;a=false;break}if(l){if(!_(t,function(n,t){if(!R(l,t)&&(s===n||u(s,n,r,e,i)))return l.push(t)})){a=false;break}}else if(s!==h&&!u(s,h,r,e,i)){a=false;break}}return i.delete(n),i.delete(t),a}function ve(n,t,r,e,u,i,o){switch(r){case"[object DataView]":if(n.byteLength!=t.byteLength||n.byteOffset!=t.byteOffset)break;n=n.buffer,t=t.buffer;case"[object ArrayBuffer]":
if(n.byteLength!=t.byteLength||!i(new di(n),new di(t)))break;return true;case"[object Boolean]":case"[object Date]":case"[object Number]":return hu(+n,+t);case"[object Error]":return n.name==t.name&&n.message==t.message;case"[object RegExp]":case"[object String]":return n==t+"";case"[object Map]":var f=L;case"[object Set]":if(f||(f=D),n.size!=t.size&&!(1&e))break;return(r=o.get(n))?r==t:(e|=2,o.set(n,t),t=_e(f(n),f(t),e,u,i,o),o.delete(n),t);case"[object Symbol]":if(eo)return eo.call(n)==eo.call(t)}
return false}function ge(n){return wo(Ce(n,F,Ve),n+"")}function de(n){return Rt(n,Lu,vo)}function ye(n){return Rt(n,Uu,go)}function be(n){for(var t=n.name+"",r=Ji[t],e=ci.call(Ji,t)?r.length:0;e--;){var u=r[e],i=u.func;if(null==i||i==n)return u.name}return t}function xe(n){return(ci.call(On,"placeholder")?On:n).placeholder}function je(){var n=On.iteratee||Pu,n=n===Pu?Gt:n;return arguments.length?n(arguments[0],arguments[1]):n}function we(n,t){var r=n.__data__,e=typeof t;return("string"==e||"number"==e||"symbol"==e||"boolean"==e?"__proto__"!==t:null===t)?r[typeof t=="string"?"string":"hash"]:r.map;
}function me(n){for(var t=Lu(n),r=t.length;r--;){var e=t[r],u=n[e];t[r]=[e,u,u===u&&!bu(u)]}return t}function Ae(n,t){var r=null==n?F:n[t];return Zt(r)?r:F}function ke(n,t,r){t=Rr(t,n);for(var e=-1,u=t.length,i=false;++e<u;){var o=$e(t[e]);if(!(i=null!=n&&r(n,o)))break;n=n[o]}return i||++e!=u?i:(u=null==n?0:n.length,!!u&&yu(u)&&Re(o,u)&&(af(n)||cf(n)))}function Ee(n){var t=n.length,r=n.constructor(t);return t&&"string"==typeof n[0]&&ci.call(n,"index")&&(r.index=n.index,r.input=n.input),r}function Oe(n){
return typeof n.constructor!="function"||Le(n)?{}:io(bi(n))}function Se(r,e,u,i){var o=r.constructor;switch(e){case"[object ArrayBuffer]":return Br(r);case"[object Boolean]":case"[object Date]":return new o(+r);case"[object DataView]":return e=i?Br(r.buffer):r.buffer,new r.constructor(e,r.byteOffset,r.byteLength);case"[object Float32Array]":case"[object Float64Array]":case"[object Int8Array]":case"[object Int16Array]":case"[object Int32Array]":case"[object Uint8Array]":case"[object Uint8ClampedArray]":
case"[object Uint16Array]":case"[object Uint32Array]":return Lr(r,i);case"[object Map]":return e=i?u(L(r),1):L(r),h(e,n,new r.constructor);case"[object Number]":case"[object String]":return new o(r);case"[object RegExp]":return e=new r.constructor(r.source,dn.exec(r)),e.lastIndex=r.lastIndex,e;case"[object Set]":return e=i?u(D(r),1):D(r),h(e,t,new r.constructor);case"[object Symbol]":return eo?ni(eo.call(r)):{}}}function Ie(n){return af(n)||cf(n)||!!(mi&&n&&n[mi])}function Re(n,t){return t=null==t?9007199254740991:t,
!!t&&(typeof n=="number"||wn.test(n))&&-1<n&&0==n%1&&n<t}function ze(n,t,r){if(!bu(r))return false;var e=typeof t;return!!("number"==e?pu(r)&&Re(t,r.length):"string"==e&&t in r)&&hu(r[t],n)}function We(n,t){if(af(n))return false;var r=typeof n;return!("number"!=r&&"symbol"!=r&&"boolean"!=r&&null!=n&&!Au(n))||(rn.test(n)||!tn.test(n)||null!=t&&n in ni(t))}function Be(n){var t=be(n),r=On[t];return typeof r=="function"&&t in Mn.prototype&&(n===r||(t=_o(r),!!t&&n===t[0]))}function Le(n){var t=n&&n.constructor;
return n===(typeof t=="function"&&t.prototype||ii)}function Ue(n,t){return function(r){return null!=r&&(r[n]===t&&(t!==F||n in ni(r)))}}function Ce(n,t,e){return t=Di(t===F?n.length-1:t,0),function(){for(var u=arguments,i=-1,o=Di(u.length-t,0),f=Hu(o);++i<o;)f[i]=u[t+i];for(i=-1,o=Hu(t+1);++i<t;)o[i]=u[i];return o[t]=e(f),r(n,this,o)}}function De(n,t,r){var e=t+"";t=wo;var u,i=Ne;return u=(u=e.match(hn))?u[1].split(pn):[],r=i(u,r),(i=r.length)&&(u=i-1,r[u]=(1<i?"& ":"")+r[u],r=r.join(2<i?", ":" "),
e=e.replace(sn,"{\n/* [wrapped with "+r+"] */\n")),t(n,e)}function Me(n){var t=0,r=0;return function(){var e=Ti(),u=16-(e-r);if(r=e,0<u){if(800<=++t)return arguments[0]}else t=0;return n.apply(F,arguments)}}function Te(n,t){var r=-1,e=n.length,u=e-1;for(t=t===F?e:t;++r<t;){var e=cr(r,u),i=n[e];n[e]=n[r],n[r]=i}return n.length=t,n}function $e(n){if(typeof n=="string"||Au(n))return n;var t=n+"";return"0"==t&&1/n==-N?"-0":t}function Fe(n){if(null!=n){try{return fi.call(n)}catch(n){}return n+""}return"";
}function Ne(n,t){return u(Z,function(r){var e="_."+r[0];t&r[1]&&!c(n,e)&&n.push(e)}),n.sort()}function Pe(n){if(n instanceof Mn)return n.clone();var t=new zn(n.__wrapped__,n.__chain__);return t.__actions__=Mr(n.__actions__),t.__index__=n.__index__,t.__values__=n.__values__,t}function Ze(n,t,r){var e=null==n?0:n.length;return e?(r=null==r?0:Ou(r),0>r&&(r=Di(e+r,0)),g(n,je(t,3),r)):-1}function qe(n,t,r){var e=null==n?0:n.length;if(!e)return-1;var u=e-1;return r!==F&&(u=Ou(r),u=0>r?Di(e+u,0):Mi(u,e-1)),
g(n,je(t,3),u,true)}function Ve(n){return(null==n?0:n.length)?kt(n,1):[]}function Ke(n){return n&&n.length?n[0]:F}function Ge(n){var t=null==n?0:n.length;return t?n[t-1]:F}function He(n,t){return n&&n.length&&t&&t.length?or(n,t):n}function Je(n){return null==n?n:Ni.call(n)}function Ye(n){if(!n||!n.length)return[];var t=0;return n=f(n,function(n){if(_u(n))return t=Di(n.length,t),true}),E(t,function(t){return l(n,j(t))})}function Qe(n,t){if(!n||!n.length)return[];var e=Ye(n);return null==t?e:l(e,function(n){
return r(t,F,n)})}function Xe(n){return n=On(n),n.__chain__=true,n}function nu(n,t){return t(n)}function tu(){return this}function ru(n,t){return(af(n)?u:oo)(n,je(t,3))}function eu(n,t){return(af(n)?i:fo)(n,je(t,3))}function uu(n,t){return(af(n)?l:Yt)(n,je(t,3))}function iu(n,t,r){return t=r?F:t,t=n&&null==t?n.length:t,le(n,128,F,F,F,F,t)}function ou(n,t){var r;if(typeof t!="function")throw new ei("Expected a function");return n=Ou(n),function(){return 0<--n&&(r=t.apply(this,arguments)),1>=n&&(t=F),
r}}function fu(n,t,r){return t=r?F:t,n=le(n,8,F,F,F,F,F,t),n.placeholder=fu.placeholder,n}function cu(n,t,r){return t=r?F:t,n=le(n,16,F,F,F,F,F,t),n.placeholder=cu.placeholder,n}function au(n,t,r){function e(t){var r=c,e=a;return c=a=F,_=t,s=n.apply(e,r)}function u(n){var r=n-p;return n-=_,p===F||r>=t||0>r||g&&n>=l}function i(){var n=Jo();if(u(n))return o(n);var r,e=jo;r=n-_,n=t-(n-p),r=g?Mi(n,l-r):n,h=e(i,r)}function o(n){return h=F,d&&c?e(n):(c=a=F,s)}function f(){var n=Jo(),r=u(n);if(c=arguments,
a=this,p=n,r){if(h===F)return _=n=p,h=jo(i,t),v?e(n):s;if(g)return h=jo(i,t),e(p)}return h===F&&(h=jo(i,t)),s}var c,a,l,s,h,p,_=0,v=false,g=false,d=true;if(typeof n!="function")throw new ei("Expected a function");return t=Iu(t)||0,bu(r)&&(v=!!r.leading,l=(g="maxWait"in r)?Di(Iu(r.maxWait)||0,t):l,d="trailing"in r?!!r.trailing:d),f.cancel=function(){h!==F&&ho(h),_=0,c=p=a=h=F},f.flush=function(){return h===F?s:o(Jo())},f}function lu(n,t){function r(){var e=arguments,u=t?t.apply(this,e):e[0],i=r.cache;return i.has(u)?i.get(u):(e=n.apply(this,e),
r.cache=i.set(u,e)||i,e)}if(typeof n!="function"||null!=t&&typeof t!="function")throw new ei("Expected a function");return r.cache=new(lu.Cache||Pn),r}function su(n){if(typeof n!="function")throw new ei("Expected a function");return function(){var t=arguments;switch(t.length){case 0:return!n.call(this);case 1:return!n.call(this,t[0]);case 2:return!n.call(this,t[0],t[1]);case 3:return!n.call(this,t[0],t[1],t[2])}return!n.apply(this,t)}}function hu(n,t){return n===t||n!==n&&t!==t}function pu(n){return null!=n&&yu(n.length)&&!gu(n);
}function _u(n){return xu(n)&&pu(n)}function vu(n){if(!xu(n))return false;var t=zt(n);return"[object Error]"==t||"[object DOMException]"==t||typeof n.message=="string"&&typeof n.name=="string"&&!wu(n)}function gu(n){return!!bu(n)&&(n=zt(n),"[object Function]"==n||"[object GeneratorFunction]"==n||"[object AsyncFunction]"==n||"[object Proxy]"==n)}function du(n){return typeof n=="number"&&n==Ou(n)}function yu(n){return typeof n=="number"&&-1<n&&0==n%1&&9007199254740991>=n}function bu(n){var t=typeof n;return null!=n&&("object"==t||"function"==t);
}function xu(n){return null!=n&&typeof n=="object"}function ju(n){return typeof n=="number"||xu(n)&&"[object Number]"==zt(n)}function wu(n){return!(!xu(n)||"[object Object]"!=zt(n))&&(n=bi(n),null===n||(n=ci.call(n,"constructor")&&n.constructor,typeof n=="function"&&n instanceof n&&fi.call(n)==hi))}function mu(n){return typeof n=="string"||!af(n)&&xu(n)&&"[object String]"==zt(n)}function Au(n){return typeof n=="symbol"||xu(n)&&"[object Symbol]"==zt(n)}function ku(n){if(!n)return[];if(pu(n))return mu(n)?$(n):Mr(n);
if(Ai&&n[Ai]){n=n[Ai]();for(var t,r=[];!(t=n.next()).done;)r.push(t.value);return r}return t=yo(n),("[object Map]"==t?L:"[object Set]"==t?D:Du)(n)}function Eu(n){return n?(n=Iu(n),n===N||n===-N?1.7976931348623157e308*(0>n?-1:1):n===n?n:0):0===n?n:0}function Ou(n){n=Eu(n);var t=n%1;return n===n?t?n-t:n:0}function Su(n){return n?gt(Ou(n),0,4294967295):0}function Iu(n){if(typeof n=="number")return n;if(Au(n))return P;if(bu(n)&&(n=typeof n.valueOf=="function"?n.valueOf():n,n=bu(n)?n+"":n),typeof n!="string")return 0===n?n:+n;
n=n.replace(cn,"");var t=bn.test(n);return t||jn.test(n)?Fn(n.slice(2),t?2:8):yn.test(n)?P:+n}function Ru(n){return Tr(n,Uu(n))}function zu(n){return null==n?"":jr(n)}function Wu(n,t,r){return n=null==n?F:It(n,t),n===F?r:n}function Bu(n,t){return null!=n&&ke(n,t,Lt)}function Lu(n){return pu(n)?Gn(n):Ht(n)}function Uu(n){if(pu(n))n=Gn(n,true);else if(bu(n)){var t,r=Le(n),e=[];for(t in n)("constructor"!=t||!r&&ci.call(n,t))&&e.push(t);n=e}else{if(t=[],null!=n)for(r in ni(n))t.push(r);n=t}return n}function Cu(n,t){
if(null==n)return{};var r=l(ye(n),function(n){return[n]});return t=je(t),ur(n,r,function(n,r){return t(n,r[0])})}function Du(n){return null==n?[]:I(n,Lu(n))}function Mu(n){return Nf(zu(n).toLowerCase())}function Tu(n){return(n=zu(n))&&n.replace(mn,rt).replace(Rn,"")}function $u(n,t,r){return n=zu(n),t=r?F:t,t===F?Ln.test(n)?n.match(Wn)||[]:n.match(_n)||[]:n.match(t)||[]}function Fu(n){return function(){return n}}function Nu(n){return n}function Pu(n){return Gt(typeof n=="function"?n:dt(n,1))}function Zu(n,t,r){
var e=Lu(t),i=St(t,e);null!=r||bu(t)&&(i.length||!e.length)||(r=t,t=n,n=this,i=St(t,Lu(t)));var o=!(bu(r)&&"chain"in r&&!r.chain),f=gu(n);return u(i,function(r){var e=t[r];n[r]=e,f&&(n.prototype[r]=function(){var t=this.__chain__;if(o||t){var r=n(this.__wrapped__);return(r.__actions__=Mr(this.__actions__)).push({func:e,args:arguments,thisArg:n}),r.__chain__=t,r}return e.apply(n,s([this.value()],arguments))})}),n}function qu(){}function Vu(n){return We(n)?j($e(n)):ir(n)}function Ku(){return[]}function Gu(){
return false}En=null==En?Zn:it.defaults(Zn.Object(),En,it.pick(Zn,Un));var Hu=En.Array,Ju=En.Date,Yu=En.Error,Qu=En.Function,Xu=En.Math,ni=En.Object,ti=En.RegExp,ri=En.String,ei=En.TypeError,ui=Hu.prototype,ii=ni.prototype,oi=En["__core-js_shared__"],fi=Qu.prototype.toString,ci=ii.hasOwnProperty,ai=0,li=function(){var n=/[^.]+$/.exec(oi&&oi.keys&&oi.keys.IE_PROTO||"");return n?"Symbol(src)_1."+n:""}(),si=ii.toString,hi=fi.call(ni),pi=Zn._,_i=ti("^"+fi.call(ci).replace(on,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),vi=Kn?En.Buffer:F,gi=En.Symbol,di=En.Uint8Array,yi=vi?vi.f:F,bi=U(ni.getPrototypeOf,ni),xi=ni.create,ji=ii.propertyIsEnumerable,wi=ui.splice,mi=gi?gi.isConcatSpreadable:F,Ai=gi?gi.iterator:F,ki=gi?gi.toStringTag:F,Ei=function(){
try{var n=Ae(ni,"defineProperty");return n({},"",{}),n}catch(n){}}(),Oi=En.clearTimeout!==Zn.clearTimeout&&En.clearTimeout,Si=Ju&&Ju.now!==Zn.Date.now&&Ju.now,Ii=En.setTimeout!==Zn.setTimeout&&En.setTimeout,Ri=Xu.ceil,zi=Xu.floor,Wi=ni.getOwnPropertySymbols,Bi=vi?vi.isBuffer:F,Li=En.isFinite,Ui=ui.join,Ci=U(ni.keys,ni),Di=Xu.max,Mi=Xu.min,Ti=Ju.now,$i=En.parseInt,Fi=Xu.random,Ni=ui.reverse,Pi=Ae(En,"DataView"),Zi=Ae(En,"Map"),qi=Ae(En,"Promise"),Vi=Ae(En,"Set"),Ki=Ae(En,"WeakMap"),Gi=Ae(ni,"create"),Hi=Ki&&new Ki,Ji={},Yi=Fe(Pi),Qi=Fe(Zi),Xi=Fe(qi),no=Fe(Vi),to=Fe(Ki),ro=gi?gi.prototype:F,eo=ro?ro.valueOf:F,uo=ro?ro.toString:F,io=function(){
function n(){}return function(t){return bu(t)?xi?xi(t):(n.prototype=t,t=new n,n.prototype=F,t):{}}}();On.templateSettings={escape:Q,evaluate:X,interpolate:nn,variable:"",imports:{_:On}},On.prototype=Sn.prototype,On.prototype.constructor=On,zn.prototype=io(Sn.prototype),zn.prototype.constructor=zn,Mn.prototype=io(Sn.prototype),Mn.prototype.constructor=Mn,Tn.prototype.clear=function(){this.__data__=Gi?Gi(null):{},this.size=0},Tn.prototype.delete=function(n){return n=this.has(n)&&delete this.__data__[n],
this.size-=n?1:0,n},Tn.prototype.get=function(n){var t=this.__data__;return Gi?(n=t[n],"__lodash_hash_undefined__"===n?F:n):ci.call(t,n)?t[n]:F},Tn.prototype.has=function(n){var t=this.__data__;return Gi?t[n]!==F:ci.call(t,n)},Tn.prototype.set=function(n,t){var r=this.__data__;return this.size+=this.has(n)?0:1,r[n]=Gi&&t===F?"__lodash_hash_undefined__":t,this},Nn.prototype.clear=function(){this.__data__=[],this.size=0},Nn.prototype.delete=function(n){var t=this.__data__;return n=lt(t,n),!(0>n)&&(n==t.length-1?t.pop():wi.call(t,n,1),
--this.size,true)},Nn.prototype.get=function(n){var t=this.__data__;return n=lt(t,n),0>n?F:t[n][1]},Nn.prototype.has=function(n){return-1<lt(this.__data__,n)},Nn.prototype.set=function(n,t){var r=this.__data__,e=lt(r,n);return 0>e?(++this.size,r.push([n,t])):r[e][1]=t,this},Pn.prototype.clear=function(){this.size=0,this.__data__={hash:new Tn,map:new(Zi||Nn),string:new Tn}},Pn.prototype.delete=function(n){return n=we(this,n).delete(n),this.size-=n?1:0,n},Pn.prototype.get=function(n){return we(this,n).get(n);
},Pn.prototype.has=function(n){return we(this,n).has(n)},Pn.prototype.set=function(n,t){var r=we(this,n),e=r.size;return r.set(n,t),this.size+=r.size==e?0:1,this},qn.prototype.add=qn.prototype.push=function(n){return this.__data__.set(n,"__lodash_hash_undefined__"),this},qn.prototype.has=function(n){return this.__data__.has(n)},Vn.prototype.clear=function(){this.__data__=new Nn,this.size=0},Vn.prototype.delete=function(n){var t=this.__data__;return n=t.delete(n),this.size=t.size,n},Vn.prototype.get=function(n){
return this.__data__.get(n)},Vn.prototype.has=function(n){return this.__data__.has(n)},Vn.prototype.set=function(n,t){var r=this.__data__;if(r instanceof Nn){var e=r.__data__;if(!Zi||199>e.length)return e.push([n,t]),this.size=++r.size,this;r=this.__data__=new Pn(e)}return r.set(n,t),this.size=r.size,this};var oo=Zr(Et),fo=Zr(Ot,true),co=qr(),ao=qr(true),lo=Hi?function(n,t){return Hi.set(n,t),n}:Nu,so=Ei?function(n,t){return Ei(n,"toString",{configurable:true,enumerable:false,value:Fu(t),writable:true})}:Nu,ho=Oi||function(n){
return Zn.clearTimeout(n)},po=Vi&&1/D(new Vi([,-0]))[1]==N?function(n){return new Vi(n)}:qu,_o=Hi?function(n){return Hi.get(n)}:qu,vo=Wi?function(n){return null==n?[]:(n=ni(n),f(Wi(n),function(t){return ji.call(n,t)}))}:Ku,go=Wi?function(n){for(var t=[];n;)s(t,vo(n)),n=bi(n);return t}:Ku,yo=zt;(Pi&&"[object DataView]"!=yo(new Pi(new ArrayBuffer(1)))||Zi&&"[object Map]"!=yo(new Zi)||qi&&"[object Promise]"!=yo(qi.resolve())||Vi&&"[object Set]"!=yo(new Vi)||Ki&&"[object WeakMap]"!=yo(new Ki))&&(yo=function(n){
var t=zt(n);if(n=(n="[object Object]"==t?n.constructor:F)?Fe(n):"")switch(n){case Yi:return"[object DataView]";case Qi:return"[object Map]";case Xi:return"[object Promise]";case no:return"[object Set]";case to:return"[object WeakMap]"}return t});var bo=oi?gu:Gu,xo=Me(lo),jo=Ii||function(n,t){return Zn.setTimeout(n,t)},wo=Me(so),mo=function(n){n=lu(n,function(n){return 500===t.size&&t.clear(),n});var t=n.cache;return n}(function(n){var t=[];return en.test(n)&&t.push(""),n.replace(un,function(n,r,e,u){
t.push(e?u.replace(vn,"$1"):r||n)}),t}),Ao=lr(function(n,t){return _u(n)?jt(n,kt(t,1,_u,true)):[]}),ko=lr(function(n,t){var r=Ge(t);return _u(r)&&(r=F),_u(n)?jt(n,kt(t,1,_u,true),je(r,2)):[]}),Eo=lr(function(n,t){var r=Ge(t);return _u(r)&&(r=F),_u(n)?jt(n,kt(t,1,_u,true),F,r):[]}),Oo=lr(function(n){var t=l(n,Sr);return t.length&&t[0]===n[0]?Ut(t):[]}),So=lr(function(n){var t=Ge(n),r=l(n,Sr);return t===Ge(r)?t=F:r.pop(),r.length&&r[0]===n[0]?Ut(r,je(t,2)):[]}),Io=lr(function(n){var t=Ge(n),r=l(n,Sr);return(t=typeof t=="function"?t:F)&&r.pop(),
r.length&&r[0]===n[0]?Ut(r,F,t):[]}),Ro=lr(He),zo=ge(function(n,t){var r=null==n?0:n.length,e=vt(n,t);return fr(n,l(t,function(n){return Re(n,r)?+n:n}).sort(Ur)),e}),Wo=lr(function(n){return wr(kt(n,1,_u,true))}),Bo=lr(function(n){var t=Ge(n);return _u(t)&&(t=F),wr(kt(n,1,_u,true),je(t,2))}),Lo=lr(function(n){var t=Ge(n),t=typeof t=="function"?t:F;return wr(kt(n,1,_u,true),F,t)}),Uo=lr(function(n,t){return _u(n)?jt(n,t):[]}),Co=lr(function(n){return Er(f(n,_u))}),Do=lr(function(n){var t=Ge(n);return _u(t)&&(t=F),
Er(f(n,_u),je(t,2))}),Mo=lr(function(n){var t=Ge(n),t=typeof t=="function"?t:F;return Er(f(n,_u),F,t)}),To=lr(Ye),$o=lr(function(n){var t=n.length,t=1<t?n[t-1]:F,t=typeof t=="function"?(n.pop(),t):F;return Qe(n,t)}),Fo=ge(function(n){function t(t){return vt(t,n)}var r=n.length,e=r?n[0]:0,u=this.__wrapped__;return!(1<r||this.__actions__.length)&&u instanceof Mn&&Re(e)?(u=u.slice(e,+e+(r?1:0)),u.__actions__.push({func:nu,args:[t],thisArg:F}),new zn(u,this.__chain__).thru(function(n){return r&&!n.length&&n.push(F),
n})):this.thru(t)}),No=Nr(function(n,t,r){ci.call(n,r)?++n[r]:_t(n,r,1)}),Po=Yr(Ze),Zo=Yr(qe),qo=Nr(function(n,t,r){ci.call(n,r)?n[r].push(t):_t(n,r,[t])}),Vo=lr(function(n,t,e){var u=-1,i=typeof t=="function",o=pu(n)?Hu(n.length):[];return oo(n,function(n){o[++u]=i?r(t,n,e):Dt(n,t,e)}),o}),Ko=Nr(function(n,t,r){_t(n,r,t)}),Go=Nr(function(n,t,r){n[r?0:1].push(t)},function(){return[[],[]]}),Ho=lr(function(n,t){if(null==n)return[];var r=t.length;return 1<r&&ze(n,t[0],t[1])?t=[]:2<r&&ze(t[0],t[1],t[2])&&(t=[t[0]]),
rr(n,kt(t,1),[])}),Jo=Si||function(){return Zn.Date.now()},Yo=lr(function(n,t,r){var e=1;if(r.length)var u=C(r,xe(Yo)),e=32|e;return le(n,e,t,r,u)}),Qo=lr(function(n,t,r){var e=3;if(r.length)var u=C(r,xe(Qo)),e=32|e;return le(t,e,n,r,u)}),Xo=lr(function(n,t){return xt(n,1,t)}),nf=lr(function(n,t,r){return xt(n,Iu(t)||0,r)});lu.Cache=Pn;var tf=lr(function(n,t){t=1==t.length&&af(t[0])?l(t[0],S(je())):l(kt(t,1),S(je()));var e=t.length;return lr(function(u){for(var i=-1,o=Mi(u.length,e);++i<o;)u[i]=t[i].call(this,u[i]);
return r(n,this,u)})}),rf=lr(function(n,t){return le(n,32,F,t,C(t,xe(rf)))}),ef=lr(function(n,t){return le(n,64,F,t,C(t,xe(ef)))}),uf=ge(function(n,t){return le(n,256,F,F,F,t)}),of=oe(Wt),ff=oe(function(n,t){return n>=t}),cf=Mt(function(){return arguments}())?Mt:function(n){return xu(n)&&ci.call(n,"callee")&&!ji.call(n,"callee")},af=Hu.isArray,lf=Hn?S(Hn):Tt,sf=Bi||Gu,hf=Jn?S(Jn):$t,pf=Yn?S(Yn):Nt,_f=Qn?S(Qn):qt,vf=Xn?S(Xn):Vt,gf=nt?S(nt):Kt,df=oe(Jt),yf=oe(function(n,t){return n<=t}),bf=Pr(function(n,t){
if(Le(t)||pu(t))Tr(t,Lu(t),n);else for(var r in t)ci.call(t,r)&&at(n,r,t[r])}),xf=Pr(function(n,t){Tr(t,Uu(t),n)}),jf=Pr(function(n,t,r,e){Tr(t,Uu(t),n,e)}),wf=Pr(function(n,t,r,e){Tr(t,Lu(t),n,e)}),mf=ge(vt),Af=lr(function(n){return n.push(F,se),r(jf,F,n)}),kf=lr(function(n){return n.push(F,he),r(Rf,F,n)}),Ef=ne(function(n,t,r){n[t]=r},Fu(Nu)),Of=ne(function(n,t,r){ci.call(n,t)?n[t].push(r):n[t]=[r]},je),Sf=lr(Dt),If=Pr(function(n,t,r){nr(n,t,r)}),Rf=Pr(function(n,t,r,e){nr(n,t,r,e)}),zf=ge(function(n,t){
var r={};if(null==n)return r;var e=false;t=l(t,function(t){return t=Rr(t,n),e||(e=1<t.length),t}),Tr(n,ye(n),r),e&&(r=dt(r,7,pe));for(var u=t.length;u--;)mr(r,t[u]);return r}),Wf=ge(function(n,t){return null==n?{}:er(n,t)}),Bf=ae(Lu),Lf=ae(Uu),Uf=Gr(function(n,t,r){return t=t.toLowerCase(),n+(r?Mu(t):t)}),Cf=Gr(function(n,t,r){return n+(r?"-":"")+t.toLowerCase()}),Df=Gr(function(n,t,r){return n+(r?" ":"")+t.toLowerCase()}),Mf=Kr("toLowerCase"),Tf=Gr(function(n,t,r){return n+(r?"_":"")+t.toLowerCase();
}),$f=Gr(function(n,t,r){return n+(r?" ":"")+Nf(t)}),Ff=Gr(function(n,t,r){return n+(r?" ":"")+t.toUpperCase()}),Nf=Kr("toUpperCase"),Pf=lr(function(n,t){try{return r(n,F,t)}catch(n){return vu(n)?n:new Yu(n)}}),Zf=ge(function(n,t){return u(t,function(t){t=$e(t),_t(n,t,Yo(n[t],n))}),n}),qf=Qr(),Vf=Qr(true),Kf=lr(function(n,t){return function(r){return Dt(r,n,t)}}),Gf=lr(function(n,t){return function(r){return Dt(n,r,t)}}),Hf=re(l),Jf=re(o),Yf=re(_),Qf=ie(),Xf=ie(true),nc=te(function(n,t){return n+t},0),tc=ce("ceil"),rc=te(function(n,t){
return n/t},1),ec=ce("floor"),uc=te(function(n,t){return n*t},1),ic=ce("round"),oc=te(function(n,t){return n-t},0);return On.after=function(n,t){if(typeof t!="function")throw new ei("Expected a function");return n=Ou(n),function(){if(1>--n)return t.apply(this,arguments)}},On.ary=iu,On.assign=bf,On.assignIn=xf,On.assignInWith=jf,On.assignWith=wf,On.at=mf,On.before=ou,On.bind=Yo,On.bindAll=Zf,On.bindKey=Qo,On.castArray=function(){if(!arguments.length)return[];var n=arguments[0];return af(n)?n:[n]},
On.chain=Xe,On.chunk=function(n,t,r){if(t=(r?ze(n,t,r):t===F)?1:Di(Ou(t),0),r=null==n?0:n.length,!r||1>t)return[];for(var e=0,u=0,i=Hu(Ri(r/t));e<r;)i[u++]=vr(n,e,e+=t);return i},On.compact=function(n){for(var t=-1,r=null==n?0:n.length,e=0,u=[];++t<r;){var i=n[t];i&&(u[e++]=i)}return u},On.concat=function(){var n=arguments.length;if(!n)return[];for(var t=Hu(n-1),r=arguments[0];n--;)t[n-1]=arguments[n];return s(af(r)?Mr(r):[r],kt(t,1))},On.cond=function(n){var t=null==n?0:n.length,e=je();return n=t?l(n,function(n){
if("function"!=typeof n[1])throw new ei("Expected a function");return[e(n[0]),n[1]]}):[],lr(function(e){for(var u=-1;++u<t;){var i=n[u];if(r(i[0],this,e))return r(i[1],this,e)}})},On.conforms=function(n){return yt(dt(n,1))},On.constant=Fu,On.countBy=No,On.create=function(n,t){var r=io(n);return null==t?r:ht(r,t)},On.curry=fu,On.curryRight=cu,On.debounce=au,On.defaults=Af,On.defaultsDeep=kf,On.defer=Xo,On.delay=nf,On.difference=Ao,On.differenceBy=ko,On.differenceWith=Eo,On.drop=function(n,t,r){var e=null==n?0:n.length;
return e?(t=r||t===F?1:Ou(t),vr(n,0>t?0:t,e)):[]},On.dropRight=function(n,t,r){var e=null==n?0:n.length;return e?(t=r||t===F?1:Ou(t),t=e-t,vr(n,0,0>t?0:t)):[]},On.dropRightWhile=function(n,t){return n&&n.length?Ar(n,je(t,3),true,true):[]},On.dropWhile=function(n,t){return n&&n.length?Ar(n,je(t,3),true):[]},On.fill=function(n,t,r,e){var u=null==n?0:n.length;if(!u)return[];for(r&&typeof r!="number"&&ze(n,t,r)&&(r=0,e=u),u=n.length,r=Ou(r),0>r&&(r=-r>u?0:u+r),e=e===F||e>u?u:Ou(e),0>e&&(e+=u),e=r>e?0:Su(e);r<e;)n[r++]=t;
return n},On.filter=function(n,t){return(af(n)?f:At)(n,je(t,3))},On.flatMap=function(n,t){return kt(uu(n,t),1)},On.flatMapDeep=function(n,t){return kt(uu(n,t),N)},On.flatMapDepth=function(n,t,r){return r=r===F?1:Ou(r),kt(uu(n,t),r)},On.flatten=Ve,On.flattenDeep=function(n){return(null==n?0:n.length)?kt(n,N):[]},On.flattenDepth=function(n,t){return null!=n&&n.length?(t=t===F?1:Ou(t),kt(n,t)):[]},On.flip=function(n){return le(n,512)},On.flow=qf,On.flowRight=Vf,On.fromPairs=function(n){for(var t=-1,r=null==n?0:n.length,e={};++t<r;){
var u=n[t];e[u[0]]=u[1]}return e},On.functions=function(n){return null==n?[]:St(n,Lu(n))},On.functionsIn=function(n){return null==n?[]:St(n,Uu(n))},On.groupBy=qo,On.initial=function(n){return(null==n?0:n.length)?vr(n,0,-1):[]},On.intersection=Oo,On.intersectionBy=So,On.intersectionWith=Io,On.invert=Ef,On.invertBy=Of,On.invokeMap=Vo,On.iteratee=Pu,On.keyBy=Ko,On.keys=Lu,On.keysIn=Uu,On.map=uu,On.mapKeys=function(n,t){var r={};return t=je(t,3),Et(n,function(n,e,u){_t(r,t(n,e,u),n)}),r},On.mapValues=function(n,t){
var r={};return t=je(t,3),Et(n,function(n,e,u){_t(r,e,t(n,e,u))}),r},On.matches=function(n){return Qt(dt(n,1))},On.matchesProperty=function(n,t){return Xt(n,dt(t,1))},On.memoize=lu,On.merge=If,On.mergeWith=Rf,On.method=Kf,On.methodOf=Gf,On.mixin=Zu,On.negate=su,On.nthArg=function(n){return n=Ou(n),lr(function(t){return tr(t,n)})},On.omit=zf,On.omitBy=function(n,t){return Cu(n,su(je(t)))},On.once=function(n){return ou(2,n)},On.orderBy=function(n,t,r,e){return null==n?[]:(af(t)||(t=null==t?[]:[t]),
r=e?F:r,af(r)||(r=null==r?[]:[r]),rr(n,t,r))},On.over=Hf,On.overArgs=tf,On.overEvery=Jf,On.overSome=Yf,On.partial=rf,On.partialRight=ef,On.partition=Go,On.pick=Wf,On.pickBy=Cu,On.property=Vu,On.propertyOf=function(n){return function(t){return null==n?F:It(n,t)}},On.pull=Ro,On.pullAll=He,On.pullAllBy=function(n,t,r){return n&&n.length&&t&&t.length?or(n,t,je(r,2)):n},On.pullAllWith=function(n,t,r){return n&&n.length&&t&&t.length?or(n,t,F,r):n},On.pullAt=zo,On.range=Qf,On.rangeRight=Xf,On.rearg=uf,On.reject=function(n,t){
return(af(n)?f:At)(n,su(je(t,3)))},On.remove=function(n,t){var r=[];if(!n||!n.length)return r;var e=-1,u=[],i=n.length;for(t=je(t,3);++e<i;){var o=n[e];t(o,e,n)&&(r.push(o),u.push(e))}return fr(n,u),r},On.rest=function(n,t){if(typeof n!="function")throw new ei("Expected a function");return t=t===F?t:Ou(t),lr(n,t)},On.reverse=Je,On.sampleSize=function(n,t,r){return t=(r?ze(n,t,r):t===F)?1:Ou(t),(af(n)?ot:hr)(n,t)},On.set=function(n,t,r){return null==n?n:pr(n,t,r)},On.setWith=function(n,t,r,e){return e=typeof e=="function"?e:F,
null==n?n:pr(n,t,r,e)},On.shuffle=function(n){return(af(n)?ft:_r)(n)},On.slice=function(n,t,r){var e=null==n?0:n.length;return e?(r&&typeof r!="number"&&ze(n,t,r)?(t=0,r=e):(t=null==t?0:Ou(t),r=r===F?e:Ou(r)),vr(n,t,r)):[]},On.sortBy=Ho,On.sortedUniq=function(n){return n&&n.length?br(n):[]},On.sortedUniqBy=function(n,t){return n&&n.length?br(n,je(t,2)):[]},On.split=function(n,t,r){return r&&typeof r!="number"&&ze(n,t,r)&&(t=r=F),r=r===F?4294967295:r>>>0,r?(n=zu(n))&&(typeof t=="string"||null!=t&&!_f(t))&&(t=jr(t),
!t&&Bn.test(n))?zr($(n),0,r):n.split(t,r):[]},On.spread=function(n,t){if(typeof n!="function")throw new ei("Expected a function");return t=null==t?0:Di(Ou(t),0),lr(function(e){var u=e[t];return e=zr(e,0,t),u&&s(e,u),r(n,this,e)})},On.tail=function(n){var t=null==n?0:n.length;return t?vr(n,1,t):[]},On.take=function(n,t,r){return n&&n.length?(t=r||t===F?1:Ou(t),vr(n,0,0>t?0:t)):[]},On.takeRight=function(n,t,r){var e=null==n?0:n.length;return e?(t=r||t===F?1:Ou(t),t=e-t,vr(n,0>t?0:t,e)):[]},On.takeRightWhile=function(n,t){
return n&&n.length?Ar(n,je(t,3),false,true):[]},On.takeWhile=function(n,t){return n&&n.length?Ar(n,je(t,3)):[]},On.tap=function(n,t){return t(n),n},On.throttle=function(n,t,r){var e=true,u=true;if(typeof n!="function")throw new ei("Expected a function");return bu(r)&&(e="leading"in r?!!r.leading:e,u="trailing"in r?!!r.trailing:u),au(n,t,{leading:e,maxWait:t,trailing:u})},On.thru=nu,On.toArray=ku,On.toPairs=Bf,On.toPairsIn=Lf,On.toPath=function(n){return af(n)?l(n,$e):Au(n)?[n]:Mr(mo(zu(n)))},On.toPlainObject=Ru,
On.transform=function(n,t,r){var e=af(n),i=e||sf(n)||gf(n);if(t=je(t,4),null==r){var o=n&&n.constructor;r=i?e?new o:[]:bu(n)&&gu(o)?io(bi(n)):{}}return(i?u:Et)(n,function(n,e,u){return t(r,n,e,u)}),r},On.unary=function(n){return iu(n,1)},On.union=Wo,On.unionBy=Bo,On.unionWith=Lo,On.uniq=function(n){return n&&n.length?wr(n):[]},On.uniqBy=function(n,t){return n&&n.length?wr(n,je(t,2)):[]},On.uniqWith=function(n,t){return t=typeof t=="function"?t:F,n&&n.length?wr(n,F,t):[]},On.unset=function(n,t){return null==n||mr(n,t);
},On.unzip=Ye,On.unzipWith=Qe,On.update=function(n,t,r){return null==n?n:pr(n,t,Ir(r)(It(n,t)),void 0)},On.updateWith=function(n,t,r,e){return e=typeof e=="function"?e:F,null!=n&&(n=pr(n,t,Ir(r)(It(n,t)),e)),n},On.values=Du,On.valuesIn=function(n){return null==n?[]:I(n,Uu(n))},On.without=Uo,On.words=$u,On.wrap=function(n,t){return rf(Ir(t),n)},On.xor=Co,On.xorBy=Do,On.xorWith=Mo,On.zip=To,On.zipObject=function(n,t){return Or(n||[],t||[],at)},On.zipObjectDeep=function(n,t){return Or(n||[],t||[],pr);
},On.zipWith=$o,On.entries=Bf,On.entriesIn=Lf,On.extend=xf,On.extendWith=jf,Zu(On,On),On.add=nc,On.attempt=Pf,On.camelCase=Uf,On.capitalize=Mu,On.ceil=tc,On.clamp=function(n,t,r){return r===F&&(r=t,t=F),r!==F&&(r=Iu(r),r=r===r?r:0),t!==F&&(t=Iu(t),t=t===t?t:0),gt(Iu(n),t,r)},On.clone=function(n){return dt(n,4)},On.cloneDeep=function(n){return dt(n,5)},On.cloneDeepWith=function(n,t){return t=typeof t=="function"?t:F,dt(n,5,t)},On.cloneWith=function(n,t){return t=typeof t=="function"?t:F,dt(n,4,t)},
On.conformsTo=function(n,t){return null==t||bt(n,t,Lu(t))},On.deburr=Tu,On.defaultTo=function(n,t){return null==n||n!==n?t:n},On.divide=rc,On.endsWith=function(n,t,r){n=zu(n),t=jr(t);var e=n.length,e=r=r===F?e:gt(Ou(r),0,e);return r-=t.length,0<=r&&n.slice(r,e)==t},On.eq=hu,On.escape=function(n){return(n=zu(n))&&Y.test(n)?n.replace(H,et):n},On.escapeRegExp=function(n){return(n=zu(n))&&fn.test(n)?n.replace(on,"\\$&"):n},On.every=function(n,t,r){var e=af(n)?o:wt;return r&&ze(n,t,r)&&(t=F),e(n,je(t,3));
},On.find=Po,On.findIndex=Ze,On.findKey=function(n,t){return v(n,je(t,3),Et)},On.findLast=Zo,On.findLastIndex=qe,On.findLastKey=function(n,t){return v(n,je(t,3),Ot)},On.floor=ec,On.forEach=ru,On.forEachRight=eu,On.forIn=function(n,t){return null==n?n:co(n,je(t,3),Uu)},On.forInRight=function(n,t){return null==n?n:ao(n,je(t,3),Uu)},On.forOwn=function(n,t){return n&&Et(n,je(t,3))},On.forOwnRight=function(n,t){return n&&Ot(n,je(t,3))},On.get=Wu,On.gt=of,On.gte=ff,On.has=function(n,t){return null!=n&&ke(n,t,Bt);
},On.hasIn=Bu,On.head=Ke,On.identity=Nu,On.includes=function(n,t,r,e){return n=pu(n)?n:Du(n),r=r&&!e?Ou(r):0,e=n.length,0>r&&(r=Di(e+r,0)),mu(n)?r<=e&&-1<n.indexOf(t,r):!!e&&-1<d(n,t,r)},On.indexOf=function(n,t,r){var e=null==n?0:n.length;return e?(r=null==r?0:Ou(r),0>r&&(r=Di(e+r,0)),d(n,t,r)):-1},On.inRange=function(n,t,r){return t=Eu(t),r===F?(r=t,t=0):r=Eu(r),n=Iu(n),n>=Mi(t,r)&&n<Di(t,r)},On.invoke=Sf,On.isArguments=cf,On.isArray=af,On.isArrayBuffer=lf,On.isArrayLike=pu,On.isArrayLikeObject=_u,
On.isBoolean=function(n){return true===n||false===n||xu(n)&&"[object Boolean]"==zt(n)},On.isBuffer=sf,On.isDate=hf,On.isElement=function(n){return xu(n)&&1===n.nodeType&&!wu(n)},On.isEmpty=function(n){if(null==n)return true;if(pu(n)&&(af(n)||typeof n=="string"||typeof n.splice=="function"||sf(n)||gf(n)||cf(n)))return!n.length;var t=yo(n);if("[object Map]"==t||"[object Set]"==t)return!n.size;if(Le(n))return!Ht(n).length;for(var r in n)if(ci.call(n,r))return false;return true},On.isEqual=function(n,t){return Ft(n,t);
},On.isEqualWith=function(n,t,r){var e=(r=typeof r=="function"?r:F)?r(n,t):F;return e===F?Ft(n,t,F,r):!!e},On.isError=vu,On.isFinite=function(n){return typeof n=="number"&&Li(n)},On.isFunction=gu,On.isInteger=du,On.isLength=yu,On.isMap=pf,On.isMatch=function(n,t){return n===t||Pt(n,t,me(t))},On.isMatchWith=function(n,t,r){return r=typeof r=="function"?r:F,Pt(n,t,me(t),r)},On.isNaN=function(n){return ju(n)&&n!=+n},On.isNative=function(n){if(bo(n))throw new Yu("Unsupported core-js use. Try https://npms.io/search?q=ponyfill.");
return Zt(n)},On.isNil=function(n){return null==n},On.isNull=function(n){return null===n},On.isNumber=ju,On.isObject=bu,On.isObjectLike=xu,On.isPlainObject=wu,On.isRegExp=_f,On.isSafeInteger=function(n){return du(n)&&-9007199254740991<=n&&9007199254740991>=n},On.isSet=vf,On.isString=mu,On.isSymbol=Au,On.isTypedArray=gf,On.isUndefined=function(n){return n===F},On.isWeakMap=function(n){return xu(n)&&"[object WeakMap]"==yo(n)},On.isWeakSet=function(n){return xu(n)&&"[object WeakSet]"==zt(n)},On.join=function(n,t){
return null==n?"":Ui.call(n,t)},On.kebabCase=Cf,On.last=Ge,On.lastIndexOf=function(n,t,r){var e=null==n?0:n.length;if(!e)return-1;var u=e;if(r!==F&&(u=Ou(r),u=0>u?Di(e+u,0):Mi(u,e-1)),t===t){for(r=u+1;r--&&n[r]!==t;);n=r}else n=g(n,b,u,true);return n},On.lowerCase=Df,On.lowerFirst=Mf,On.lt=df,On.lte=yf,On.max=function(n){return n&&n.length?mt(n,Nu,Wt):F},On.maxBy=function(n,t){return n&&n.length?mt(n,je(t,2),Wt):F},On.mean=function(n){return x(n,Nu)},On.meanBy=function(n,t){return x(n,je(t,2))},On.min=function(n){
return n&&n.length?mt(n,Nu,Jt):F},On.minBy=function(n,t){return n&&n.length?mt(n,je(t,2),Jt):F},On.stubArray=Ku,On.stubFalse=Gu,On.stubObject=function(){return{}},On.stubString=function(){return""},On.stubTrue=function(){return true},On.multiply=uc,On.nth=function(n,t){return n&&n.length?tr(n,Ou(t)):F},On.noConflict=function(){return Zn._===this&&(Zn._=pi),this},On.noop=qu,On.now=Jo,On.pad=function(n,t,r){n=zu(n);var e=(t=Ou(t))?T(n):0;return!t||e>=t?n:(t=(t-e)/2,ee(zi(t),r)+n+ee(Ri(t),r))},On.padEnd=function(n,t,r){
n=zu(n);var e=(t=Ou(t))?T(n):0;return t&&e<t?n+ee(t-e,r):n},On.padStart=function(n,t,r){n=zu(n);var e=(t=Ou(t))?T(n):0;return t&&e<t?ee(t-e,r)+n:n},On.parseInt=function(n,t,r){return r||null==t?t=0:t&&(t=+t),$i(zu(n).replace(an,""),t||0)},On.random=function(n,t,r){if(r&&typeof r!="boolean"&&ze(n,t,r)&&(t=r=F),r===F&&(typeof t=="boolean"?(r=t,t=F):typeof n=="boolean"&&(r=n,n=F)),n===F&&t===F?(n=0,t=1):(n=Eu(n),t===F?(t=n,n=0):t=Eu(t)),n>t){var e=n;n=t,t=e}return r||n%1||t%1?(r=Fi(),Mi(n+r*(t-n+$n("1e-"+((r+"").length-1))),t)):cr(n,t);
},On.reduce=function(n,t,r){var e=af(n)?h:m,u=3>arguments.length;return e(n,je(t,4),r,u,oo)},On.reduceRight=function(n,t,r){var e=af(n)?p:m,u=3>arguments.length;return e(n,je(t,4),r,u,fo)},On.repeat=function(n,t,r){return t=(r?ze(n,t,r):t===F)?1:Ou(t),ar(zu(n),t)},On.replace=function(){var n=arguments,t=zu(n[0]);return 3>n.length?t:t.replace(n[1],n[2])},On.result=function(n,t,r){t=Rr(t,n);var e=-1,u=t.length;for(u||(u=1,n=F);++e<u;){var i=null==n?F:n[$e(t[e])];i===F&&(e=u,i=r),n=gu(i)?i.call(n):i;
}return n},On.round=ic,On.runInContext=w,On.sample=function(n){return(af(n)?tt:sr)(n)},On.size=function(n){if(null==n)return 0;if(pu(n))return mu(n)?T(n):n.length;var t=yo(n);return"[object Map]"==t||"[object Set]"==t?n.size:Ht(n).length},On.snakeCase=Tf,On.some=function(n,t,r){var e=af(n)?_:gr;return r&&ze(n,t,r)&&(t=F),e(n,je(t,3))},On.sortedIndex=function(n,t){return dr(n,t)},On.sortedIndexBy=function(n,t,r){return yr(n,t,je(r,2))},On.sortedIndexOf=function(n,t){var r=null==n?0:n.length;if(r){
var e=dr(n,t);if(e<r&&hu(n[e],t))return e}return-1},On.sortedLastIndex=function(n,t){return dr(n,t,true)},On.sortedLastIndexBy=function(n,t,r){return yr(n,t,je(r,2),true)},On.sortedLastIndexOf=function(n,t){if(null==n?0:n.length){var r=dr(n,t,true)-1;if(hu(n[r],t))return r}return-1},On.startCase=$f,On.startsWith=function(n,t,r){return n=zu(n),r=null==r?0:gt(Ou(r),0,n.length),t=jr(t),n.slice(r,r+t.length)==t},On.subtract=oc,On.sum=function(n){return n&&n.length?k(n,Nu):0},On.sumBy=function(n,t){return n&&n.length?k(n,je(t,2)):0;
},On.template=function(n,t,r){var e=On.templateSettings;r&&ze(n,t,r)&&(t=F),n=zu(n),t=jf({},t,e,se),r=jf({},t.imports,e.imports,se);var u,i,o=Lu(r),f=I(r,o),c=0;r=t.interpolate||An;var a="__p+='";r=ti((t.escape||An).source+"|"+r.source+"|"+(r===nn?gn:An).source+"|"+(t.evaluate||An).source+"|$","g");var l="sourceURL"in t?"//# sourceURL="+t.sourceURL+"\n":"";if(n.replace(r,function(t,r,e,o,f,l){return e||(e=o),a+=n.slice(c,l).replace(kn,B),r&&(u=true,a+="'+__e("+r+")+'"),f&&(i=true,a+="';"+f+";\n__p+='"),
e&&(a+="'+((__t=("+e+"))==null?'':__t)+'"),c=l+t.length,t}),a+="';",(t=t.variable)||(a="with(obj){"+a+"}"),a=(i?a.replace(q,""):a).replace(V,"$1").replace(K,"$1;"),a="function("+(t||"obj")+"){"+(t?"":"obj||(obj={});")+"var __t,__p=''"+(u?",__e=_.escape":"")+(i?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+a+"return __p}",t=Pf(function(){return Qu(o,l+"return "+a).apply(F,f)}),t.source=a,vu(t))throw t;return t},On.times=function(n,t){if(n=Ou(n),1>n||9007199254740991<n)return[];
var r=4294967295,e=Mi(n,4294967295);for(t=je(t),n-=4294967295,e=E(e,t);++r<n;)t(r);return e},On.toFinite=Eu,On.toInteger=Ou,On.toLength=Su,On.toLower=function(n){return zu(n).toLowerCase()},On.toNumber=Iu,On.toSafeInteger=function(n){return n?gt(Ou(n),-9007199254740991,9007199254740991):0===n?n:0},On.toString=zu,On.toUpper=function(n){return zu(n).toUpperCase()},On.trim=function(n,t,r){return(n=zu(n))&&(r||t===F)?n.replace(cn,""):n&&(t=jr(t))?(n=$(n),r=$(t),t=z(n,r),r=W(n,r)+1,zr(n,t,r).join("")):n;
},On.trimEnd=function(n,t,r){return(n=zu(n))&&(r||t===F)?n.replace(ln,""):n&&(t=jr(t))?(n=$(n),t=W(n,$(t))+1,zr(n,0,t).join("")):n},On.trimStart=function(n,t,r){return(n=zu(n))&&(r||t===F)?n.replace(an,""):n&&(t=jr(t))?(n=$(n),t=z(n,$(t)),zr(n,t).join("")):n},On.truncate=function(n,t){var r=30,e="...";if(bu(t))var u="separator"in t?t.separator:u,r="length"in t?Ou(t.length):r,e="omission"in t?jr(t.omission):e;n=zu(n);var i=n.length;if(Bn.test(n))var o=$(n),i=o.length;if(r>=i)return n;if(i=r-T(e),1>i)return e;
if(r=o?zr(o,0,i).join(""):n.slice(0,i),u===F)return r+e;if(o&&(i+=r.length-i),_f(u)){if(n.slice(i).search(u)){var f=r;for(u.global||(u=ti(u.source,zu(dn.exec(u))+"g")),u.lastIndex=0;o=u.exec(f);)var c=o.index;r=r.slice(0,c===F?i:c)}}else n.indexOf(jr(u),i)!=i&&(u=r.lastIndexOf(u),-1<u&&(r=r.slice(0,u)));return r+e},On.unescape=function(n){return(n=zu(n))&&J.test(n)?n.replace(G,ut):n},On.uniqueId=function(n){var t=++ai;return zu(n)+t},On.upperCase=Ff,On.upperFirst=Nf,On.each=ru,On.eachRight=eu,On.first=Ke,
Zu(On,function(){var n={};return Et(On,function(t,r){ci.call(On.prototype,r)||(n[r]=t)}),n}(),{chain:false}),On.VERSION="4.17.3",u("bind bindKey curry curryRight partial partialRight".split(" "),function(n){On[n].placeholder=On}),u(["drop","take"],function(n,t){Mn.prototype[n]=function(r){r=r===F?1:Di(Ou(r),0);var e=this.__filtered__&&!t?new Mn(this):this.clone();return e.__filtered__?e.__takeCount__=Mi(r,e.__takeCount__):e.__views__.push({size:Mi(r,4294967295),type:n+(0>e.__dir__?"Right":"")}),e},Mn.prototype[n+"Right"]=function(t){
return this.reverse()[n](t).reverse()}}),u(["filter","map","takeWhile"],function(n,t){var r=t+1,e=1==r||3==r;Mn.prototype[n]=function(n){var t=this.clone();return t.__iteratees__.push({iteratee:je(n,3),type:r}),t.__filtered__=t.__filtered__||e,t}}),u(["head","last"],function(n,t){var r="take"+(t?"Right":"");Mn.prototype[n]=function(){return this[r](1).value()[0]}}),u(["initial","tail"],function(n,t){var r="drop"+(t?"":"Right");Mn.prototype[n]=function(){return this.__filtered__?new Mn(this):this[r](1);
}}),Mn.prototype.compact=function(){return this.filter(Nu)},Mn.prototype.find=function(n){return this.filter(n).head()},Mn.prototype.findLast=function(n){return this.reverse().find(n)},Mn.prototype.invokeMap=lr(function(n,t){return typeof n=="function"?new Mn(this):this.map(function(r){return Dt(r,n,t)})}),Mn.prototype.reject=function(n){return this.filter(su(je(n)))},Mn.prototype.slice=function(n,t){n=Ou(n);var r=this;return r.__filtered__&&(0<n||0>t)?new Mn(r):(0>n?r=r.takeRight(-n):n&&(r=r.drop(n)),
t!==F&&(t=Ou(t),r=0>t?r.dropRight(-t):r.take(t-n)),r)},Mn.prototype.takeRightWhile=function(n){return this.reverse().takeWhile(n).reverse()},Mn.prototype.toArray=function(){return this.take(4294967295)},Et(Mn.prototype,function(n,t){var r=/^(?:filter|find|map|reject)|While$/.test(t),e=/^(?:head|last)$/.test(t),u=On[e?"take"+("last"==t?"Right":""):t],i=e||/^find/.test(t);u&&(On.prototype[t]=function(){function t(n){return n=u.apply(On,s([n],f)),e&&h?n[0]:n}var o=this.__wrapped__,f=e?[1]:arguments,c=o instanceof Mn,a=f[0],l=c||af(o);
l&&r&&typeof a=="function"&&1!=a.length&&(c=l=false);var h=this.__chain__,p=!!this.__actions__.length,a=i&&!h,c=c&&!p;return!i&&l?(o=c?o:new Mn(this),o=n.apply(o,f),o.__actions__.push({func:nu,args:[t],thisArg:F}),new zn(o,h)):a&&c?n.apply(this,f):(o=this.thru(t),a?e?o.value()[0]:o.value():o)})}),u("pop push shift sort splice unshift".split(" "),function(n){var t=ui[n],r=/^(?:push|sort|unshift)$/.test(n)?"tap":"thru",e=/^(?:pop|shift)$/.test(n);On.prototype[n]=function(){var n=arguments;if(e&&!this.__chain__){
var u=this.value();return t.apply(af(u)?u:[],n)}return this[r](function(r){return t.apply(af(r)?r:[],n)})}}),Et(Mn.prototype,function(n,t){var r=On[t];if(r){var e=r.name+"";(Ji[e]||(Ji[e]=[])).push({name:t,func:r})}}),Ji[Xr(F,2).name]=[{name:"wrapper",func:F}],Mn.prototype.clone=function(){var n=new Mn(this.__wrapped__);return n.__actions__=Mr(this.__actions__),n.__dir__=this.__dir__,n.__filtered__=this.__filtered__,n.__iteratees__=Mr(this.__iteratees__),n.__takeCount__=this.__takeCount__,n.__views__=Mr(this.__views__),
n},Mn.prototype.reverse=function(){if(this.__filtered__){var n=new Mn(this);n.__dir__=-1,n.__filtered__=true}else n=this.clone(),n.__dir__*=-1;return n},Mn.prototype.value=function(){var n,t=this.__wrapped__.value(),r=this.__dir__,e=af(t),u=0>r,i=e?t.length:0;n=i;for(var o=this.__views__,f=0,c=-1,a=o.length;++c<a;){var l=o[c],s=l.size;switch(l.type){case"drop":f+=s;break;case"dropRight":n-=s;break;case"take":n=Mi(n,f+s);break;case"takeRight":f=Di(f,n-s)}}if(n={start:f,end:n},o=n.start,f=n.end,n=f-o,
o=u?f:o-1,f=this.__iteratees__,c=f.length,a=0,l=Mi(n,this.__takeCount__),!e||!u&&i==n&&l==n)return kr(t,this.__actions__);e=[];n:for(;n--&&a<l;){for(o+=r,u=-1,i=t[o];++u<c;){var h=f[u],s=h.type,h=(0,h.iteratee)(i);if(2==s)i=h;else if(!h){if(1==s)continue n;break n}}e[a++]=i}return e},On.prototype.at=Fo,On.prototype.chain=function(){return Xe(this)},On.prototype.commit=function(){return new zn(this.value(),this.__chain__)},On.prototype.next=function(){this.__values__===F&&(this.__values__=ku(this.value()));
var n=this.__index__>=this.__values__.length;return{done:n,value:n?F:this.__values__[this.__index__++]}},On.prototype.plant=function(n){for(var t,r=this;r instanceof Sn;){var e=Pe(r);e.__index__=0,e.__values__=F,t?u.__wrapped__=e:t=e;var u=e,r=r.__wrapped__}return u.__wrapped__=n,t},On.prototype.reverse=function(){var n=this.__wrapped__;return n instanceof Mn?(this.__actions__.length&&(n=new Mn(this)),n=n.reverse(),n.__actions__.push({func:nu,args:[Je],thisArg:F}),new zn(n,this.__chain__)):this.thru(Je);
},On.prototype.toJSON=On.prototype.valueOf=On.prototype.value=function(){return kr(this.__wrapped__,this.__actions__)},On.prototype.first=On.prototype.head,Ai&&(On.prototype[Ai]=tu),On}();typeof define=="function"&&typeof define.amd=="object"&&define.amd?(Zn._=it, define(function(){return it})):Vn?((Vn.exports=it)._=it,qn._=it):Zn._=it}).call(this);;
(function () {
    var defaultFrameRate = 20,
        // This is the default fallback throttle function
        framerateThrottle = function (callback) {
            return _.throttle(callback, 1 / (defaultFrameRate * 1000));
        };

    // Feature detection - should have requestAnimationFrame
    if (window.requestAnimationFrame) {
        framerateThrottle = function (callback) {
            var mayUpdate = false,
                update = function () {
                    mayUpdate = true;
                    window.requestAnimationFrame(update);
                };

            // initial update
            window.requestAnimationFrame(update);

            // the function called by, e.g. an input event
            return function () {
                var thisArg = this;
                // discard the invocation when mayUpdate
                // is false (i.e. is throttled)
                if (mayUpdate) {
                    mayUpdate = false;
                    return callback.apply(thisArg, arguments);
                }
            };
        };
    }

    // Mix in the framerate throttle to lodash
    _.mixin({
        framerateThrottle: framerateThrottle
    });
}());;
/*!
 * Lightbox v2.9.0
 * by Lokesh Dhakar
 *
 * More info:
 * http://lokeshdhakar.com/projects/lightbox2/
 *
 * Copyright 2007, 2015 Lokesh Dhakar
 * Released under the MIT license
 * https://github.com/lokesh/lightbox2/blob/master/LICENSE
 *
 * @preserve
 */

// Uses Node, AMD or browser globals to create a module.
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['jquery'], factory);
    } else if (typeof exports === 'object') {
        // Node. Does not work with strict CommonJS, but
        // only CommonJS-like environments that support module.exports,
        // like Node.
        module.exports = factory(require('jquery'));
    } else {
        // Browser globals (root is window)
        root.lightbox = factory(root.jQuery);
    }
}(this, function ($) {

    function Lightbox(options) {
        this.album = [];
        this.currentImageIndex = void 0;
        this.init();

        // options
        this.options = $.extend({}, this.constructor.defaults);
        this.option(options);
    }

    // Descriptions of all options available on the demo site:
    // http://lokeshdhakar.com/projects/lightbox2/index.html#options
    Lightbox.defaults = {
        albumLabel: 'Image %1 of %2',
        alwaysShowNavOnTouchDevices: false,
        fadeDuration: 600,
        fitImagesInViewport: true,
        imageFadeDuration: 600,
        // maxWidth: 800,
        // maxHeight: 600,
        positionFromTop: 50,
        resizeDuration: 700,
        showImageNumberLabel: true,
        wrapAround: false,
        disableScrolling: false,
        imageScrollWithPage: false,
        /*
        Sanitize Title
        If the caption data is trusted, for example you are hardcoding it in, then leave this to false.
        This will free you to add html tags, such as links, in the caption.
    
        If the caption data is user submitted or from some other untrusted source, then set this to true
        to prevent xss and other injection attacks.
         */
        sanitizeTitle: false
    };

    Lightbox.prototype.option = function (options) {
        $.extend(this.options, options);
    };

    Lightbox.prototype.imageCountLabel = function (currentImageNum, totalImages) {
        return this.options.albumLabel.replace(/%1/g, currentImageNum).replace(/%2/g, totalImages);
    };

    Lightbox.prototype.init = function () {
        var self = this;
        // Both enable and build methods require the body tag to be in the DOM.
        $(document).ready(function () {
            self.enable();
            self.build();
        });
    };

    // Loop through anchors and areamaps looking for either data-lightbox attributes or rel attributes
    // that contain 'lightbox'. When these are clicked, start lightbox.
    Lightbox.prototype.enable = function () {
        var self = this;
        $('body').on('click', 'a[rel^=lightbox], area[rel^=lightbox], a[data-lightbox], area[data-lightbox]', function (event) {
            self.start($(event.currentTarget));
            return false;
        });
    };

    // Build html for the lightbox and the overlay.
    // Attach event handlers to the new DOM elements. click click click
    Lightbox.prototype.build = function () {
        var self = this;
        var lightBoxClass = '"lightbox"';
        if (this.options.imageScrollWithPage) {
            lightBoxClass = '"lightboxFixed"';
        }

        $('<div id="lightboxOverlay" class="lightboxOverlay"></div><div id="lightbox" class=' + lightBoxClass + '><div class="lb-outerContainer"><div class="lb-container"><img class="lb-image" src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==" /><div class="lb-nav"><a class="lb-prev" href="" ></a><a class="lb-next" href="" ></a></div><div class="lb-loader"><a class="lb-cancel"></a></div></div></div><div class="lb-dataContainer"><div class="lb-data"><div class="lb-details"><span class="lb-caption"></span><span class="lb-number"></span></div><div class="lb-closeContainer"><a class="lb-close"></a></div></div></div></div>').appendTo($('body'));

        // Cache jQuery objects
        this.$lightbox = $('#lightbox');
        this.$overlay = $('#lightboxOverlay');
        this.$outerContainer = this.$lightbox.find('.lb-outerContainer');
        this.$container = this.$lightbox.find('.lb-container');
        this.$image = this.$lightbox.find('.lb-image');
        this.$nav = this.$lightbox.find('.lb-nav');

        // Store css values for future lookup
        this.containerPadding = {
            top: parseInt(this.$container.css('padding-top'), 10),
            right: parseInt(this.$container.css('padding-right'), 10),
            bottom: parseInt(this.$container.css('padding-bottom'), 10),
            left: parseInt(this.$container.css('padding-left'), 10)
        };

        this.imageBorderWidth = {
            top: parseInt(this.$image.css('border-top-width'), 10),
            right: parseInt(this.$image.css('border-right-width'), 10),
            bottom: parseInt(this.$image.css('border-bottom-width'), 10),
            left: parseInt(this.$image.css('border-left-width'), 10)
        };

        // Attach event handlers to the newly minted DOM elements
        this.$overlay.hide().on('click', function () {
            self.end();
            return false;
        });

        this.$lightbox.hide().on('click', function (event) {
            if ($(event.target).attr('id') === 'lightbox') {
                self.end();
            }
            return false;
        });

        this.$outerContainer.on('click', function (event) {
            if ($(event.target).attr('id') === 'lightbox') {
                self.end();
            }
            return false;
        });

        this.$lightbox.find('.lb-prev').on('click', function () {
            if (self.currentImageIndex === 0) {
                self.changeImage(self.album.length - 1);
            } else {
                self.changeImage(self.currentImageIndex - 1);
            }
            return false;
        });

        this.$lightbox.find('.lb-next').on('click', function () {
            if (self.currentImageIndex === self.album.length - 1) {
                self.changeImage(0);
            } else {
                self.changeImage(self.currentImageIndex + 1);
            }
            return false;
        });

        /*
          Show context menu for image on right-click
    
          There is a div containing the navigation that spans the entire image and lives above of it. If
          you right-click, you are right clicking this div and not the image. This prevents users from
          saving the image or using other context menu actions with the image.
    
          To fix this, when we detect the right mouse button is pressed down, but not yet clicked, we
          set pointer-events to none on the nav div. This is so that the upcoming right-click event on
          the next mouseup will bubble down to the image. Once the right-click/contextmenu event occurs
          we set the pointer events back to auto for the nav div so it can capture hover and left-click
          events as usual.
         */
        this.$nav.on('mousedown', function (event) {
            if (event.which === 3) {
                self.$nav.css('pointer-events', 'none');

                self.$lightbox.one('contextmenu', function () {
                    setTimeout(function () {
                        this.$nav.css('pointer-events', 'auto');
                    }.bind(self), 0);
                });
            }
        });


        this.$lightbox.find('.lb-loader, .lb-close').on('click', function () {
            self.end();
            return false;
        });
    };

    // Show overlay and lightbox. If the image is part of a set, add siblings to album array.
    Lightbox.prototype.start = function ($link) {
        var self = this;
        var $window = $(window);

        $window.on('resize', $.proxy(this.sizeOverlay, this));

        $('select, object, embed').css({
            visibility: 'hidden'
        });

        this.sizeOverlay();

        this.album = [];
        var imageNumber = 0;

        function addToAlbum($link) {
            self.album.push({
                link: $link.attr('href'),
                title: $link.attr('data-title') || $link.attr('title'),
                ribbonOverlay: $link.attr('data-ribbon-overlay')
            });
        }

        // Support both data-lightbox attribute and rel attribute implementations       
        var dataLightboxValue = $link.attr('data-lightbox');
        var dataRibbonOverlayValue = $link.attr('data-ribbon-overlay');
        var doShowRibbonOverlay = typeof dataRibbonOverlayValue !== 'undefined'
        var $links;

        if (dataLightboxValue) {
            $links = $($link.prop('tagName') + '[data-lightbox="' + dataLightboxValue + '"]');

            if (doShowRibbonOverlay) {
                $links.attr('data-ribbon-overlay', dataRibbonOverlayValue);
            }

            for (var i = 0; i < $links.length; i = ++i) {
                addToAlbum($($links[i]));
                if ($links[i] === $link[0]) {
                    imageNumber = i;
                }
            }
        } else {
            if ($link.attr('rel') === 'lightbox') {
                // If image is not part of a set
                addToAlbum($link);
            } else {
                // If image is part of a set
                $links = $($link.prop('tagName') + '[rel="' + $link.attr('rel') + '"]');
                for (var j = 0; j < $links.length; j = ++j) {
                    addToAlbum($($links[j]));
                    if ($links[j] === $link[0]) {
                        imageNumber = j;
                    }
                }
            }
        }

        // Position Lightbox
        var top;

        if (this.options.imageScrollWithPage) {
            top = this.options.positionFromTop;
        }
        else {
            top = $window.scrollTop() + this.options.positionFromTop;
        }

        var left = $window.scrollLeft();
        this.$lightbox.css({
            top: top + 'px',
            left: left + 'px'
        }).fadeIn(this.options.fadeDuration);

        // Disable scrolling of the page while open
        if (this.options.disableScrolling) {
            $('body').addClass('lb-disable-scrolling');
        }

        this.changeImage(imageNumber);
    };

    // Hide most UI elements in preparation for the animated resizing of the lightbox.
    Lightbox.prototype.changeImage = function (imageNumber) {
        var self = this;

        this.disableKeyboardNav();
        var $image = this.$lightbox.find('.lb-image');

        this.$overlay.fadeIn(this.options.fadeDuration);

        $('.lb-loader').fadeIn('slow');
        this.$lightbox.find('.lb-image, .lb-nav, .lb-prev, .lb-next, .lb-dataContainer, .lb-numbers, .lb-caption').hide();

        this.$outerContainer.addClass('animating');

        // When image to show is preloaded, we send the width and height to sizeContainer()
        var preloader = new Image();
        preloader.onload = function () {
            var $preloader;
            var imageHeight;
            var imageWidth;
            var maxImageHeight;
            var maxImageWidth;
            var windowHeight;
            var windowWidth;

            $image.attr('src', self.album[imageNumber].link);

            var ribbonOverlay = self.album[imageNumber].ribbonOverlay;
            if (ribbonOverlay) {
                $image.after('<div id="lightbox-cover-ribbon-overlay" class="cover-ribbon-overlay cover-ribbon-overlay-large" style="display: block;">' + ribbonOverlay + '</div>');
            }

            $preloader = $(preloader);

            $image.width(preloader.width);
            $image.height(preloader.height);

            if (self.options.fitImagesInViewport) {
                // Fit image inside the viewport.
                // Take into account the border around the image and an additional 10px gutter on each side.

                windowWidth = $(window).width();
                windowHeight = $(window).height();
                maxImageWidth = windowWidth - self.containerPadding.left - self.containerPadding.right - self.imageBorderWidth.left - self.imageBorderWidth.right - 20;
                maxImageHeight = windowHeight - self.containerPadding.top - self.containerPadding.bottom - self.imageBorderWidth.top - self.imageBorderWidth.bottom - 120;

                // Check if image size is larger then maxWidth|maxHeight in settings
                if (self.options.maxWidth && self.options.maxWidth < maxImageWidth) {
                    maxImageWidth = self.options.maxWidth;
                }
                if (self.options.maxHeight && self.options.maxHeight < maxImageWidth) {
                    maxImageHeight = self.options.maxHeight;
                }

                // Is the current image's width or height is greater than the maxImageWidth or maxImageHeight
                // option than we need to size down while maintaining the aspect ratio.
                if ((preloader.width > maxImageWidth) || (preloader.height > maxImageHeight)) {
                    if ((preloader.width / maxImageWidth) > (preloader.height / maxImageHeight)) {
                        imageWidth = maxImageWidth;
                        imageHeight = parseInt(preloader.height / (preloader.width / imageWidth), 10);
                        $image.width(imageWidth);
                        $image.height(imageHeight);
                    } else {
                        imageHeight = maxImageHeight;
                        imageWidth = parseInt(preloader.width / (preloader.height / imageHeight), 10);
                        $image.width(imageWidth);
                        $image.height(imageHeight);
                    }
                }
            }
            self.sizeContainer($image.width(), $image.height());
        };

        preloader.src = this.album[imageNumber].link;
        this.currentImageIndex = imageNumber;
    };

    // Stretch overlay to fit the viewport
    Lightbox.prototype.sizeOverlay = function () {
        this.$overlay
            .width($(document).width())
            .height($(document).height());
    };

    // Animate the size of the lightbox to fit the image we are showing
    Lightbox.prototype.sizeContainer = function (imageWidth, imageHeight) {
        var self = this;

        var oldWidth = this.$outerContainer.outerWidth();
        var oldHeight = this.$outerContainer.outerHeight();
        var newWidth = imageWidth + this.containerPadding.left + this.containerPadding.right + this.imageBorderWidth.left + this.imageBorderWidth.right;
        var newHeight = imageHeight + this.containerPadding.top + this.containerPadding.bottom + this.imageBorderWidth.top + this.imageBorderWidth.bottom;

        function postResize() {
            self.$lightbox.find('.lb-dataContainer').width(newWidth);
            self.$lightbox.find('.lb-prevLink').height(newHeight);
            self.$lightbox.find('.lb-nextLink').height(newHeight);
            self.showImage();
        }

        if (oldWidth !== newWidth || oldHeight !== newHeight) {
            this.$outerContainer.animate({
                width: newWidth,
                height: newHeight
            }, this.options.resizeDuration, 'swing', function () {
                postResize();
            });
        } else {
            postResize();
        }
    };

    // Display the image and its details and begin preload neighboring images.
    Lightbox.prototype.showImage = function () {
        this.$lightbox.find('.lb-loader').stop(true).hide();
        this.$lightbox.find('.lb-image').fadeIn(this.options.imageFadeDuration);

        this.updateNav();
        this.updateDetails();
        this.preloadNeighboringImages();
        this.enableKeyboardNav();
    };

    // Display previous and next navigation if appropriate.
    Lightbox.prototype.updateNav = function () {
        // Check to see if the browser supports touch events. If so, we take the conservative approach
        // and assume that mouse hover events are not supported and always show prev/next navigation
        // arrows in image sets.
        var alwaysShowNav = false;
        try {
            document.createEvent('TouchEvent');
            alwaysShowNav = (this.options.alwaysShowNavOnTouchDevices) ? true : false;
        } catch (e) { }

        this.$lightbox.find('.lb-nav').show();

        if (this.album.length > 1) {
            if (this.options.wrapAround) {
                if (alwaysShowNav) {
                    this.$lightbox.find('.lb-prev, .lb-next').css('opacity', '1');
                }
                this.$lightbox.find('.lb-prev, .lb-next').show();
            } else {
                if (this.currentImageIndex > 0) {
                    this.$lightbox.find('.lb-prev').show();
                    if (alwaysShowNav) {
                        this.$lightbox.find('.lb-prev').css('opacity', '1');
                    }
                }
                if (this.currentImageIndex < this.album.length - 1) {
                    this.$lightbox.find('.lb-next').show();
                    if (alwaysShowNav) {
                        this.$lightbox.find('.lb-next').css('opacity', '1');
                    }
                }
            }
        }
    };

    // Display caption, image number, and closing button.
    Lightbox.prototype.updateDetails = function () {
        var self = this;

        // Enable anchor clicks in the injected caption html.
        // Thanks Nate Wright for the fix. @https://github.com/NateWr
        if (typeof this.album[this.currentImageIndex].title !== 'undefined' &&
            this.album[this.currentImageIndex].title !== '') {
            var $caption = this.$lightbox.find('.lb-caption');
            if (this.options.sanitizeTitle) {
                $caption.text(this.album[this.currentImageIndex].title);
            } else {
                $caption.html(this.album[this.currentImageIndex].title);
            }
            $caption.fadeIn('fast')
                .find('a').on('click', function (event) {
                    if ($(this).attr('target') !== undefined) {
                        window.open($(this).attr('href'), $(this).attr('target'));
                    } else {
                        location.href = $(this).attr('href');
                    }
                });
        }

        if (this.album.length > 1 && this.options.showImageNumberLabel) {
            var labelText = this.imageCountLabel(this.currentImageIndex + 1, this.album.length);
            this.$lightbox.find('.lb-number').text(labelText).fadeIn('fast');
        } else {
            this.$lightbox.find('.lb-number').hide();
        }

        this.$outerContainer.removeClass('animating');

        this.$lightbox.find('.lb-dataContainer').fadeIn(this.options.resizeDuration, function () {
            return self.sizeOverlay();
        });
    };

    // Preload previous and next images in set.
    Lightbox.prototype.preloadNeighboringImages = function () {
        if (this.album.length > this.currentImageIndex + 1) {
            var preloadNext = new Image();
            preloadNext.src = this.album[this.currentImageIndex + 1].link;
        }
        if (this.currentImageIndex > 0) {
            var preloadPrev = new Image();
            preloadPrev.src = this.album[this.currentImageIndex - 1].link;
        }
    };

    Lightbox.prototype.enableKeyboardNav = function () {
        $(document).on('keyup.keyboard', $.proxy(this.keyboardAction, this));
    };

    Lightbox.prototype.disableKeyboardNav = function () {
        $(document).off('.keyboard');
    };

    Lightbox.prototype.keyboardAction = function (event) {
        var KEYCODE_ESC = 27;
        var KEYCODE_LEFTARROW = 37;
        var KEYCODE_RIGHTARROW = 39;

        var keycode = event.keyCode;
        var key = String.fromCharCode(keycode).toLowerCase();
        if (keycode === KEYCODE_ESC || key.match(/x|o|c/)) {
            this.end();
        } else if (key === 'p' || keycode === KEYCODE_LEFTARROW) {
            if (this.currentImageIndex !== 0) {
                this.changeImage(this.currentImageIndex - 1);
            } else if (this.options.wrapAround && this.album.length > 1) {
                this.changeImage(this.album.length - 1);
            }
        } else if (key === 'n' || keycode === KEYCODE_RIGHTARROW) {
            if (this.currentImageIndex !== this.album.length - 1) {
                this.changeImage(this.currentImageIndex + 1);
            } else if (this.options.wrapAround && this.album.length > 1) {
                this.changeImage(0);
            }
        }
    };

    // Closing time. :-(
    Lightbox.prototype.end = function () {
        this.disableKeyboardNav();
        $(window).off('resize', this.sizeOverlay);
        this.$lightbox.fadeOut(this.options.fadeDuration);
        this.$overlay.fadeOut(this.options.fadeDuration);
        $('select, object, embed').css({
            visibility: 'visible'
        });
        if (this.options.disableScrolling) {
            $('body').removeClass('lb-disable-scrolling');
        }
        $("#lightbox-cover-ribbon-overlay").remove();
    };

    return new Lightbox();
}));
;
// jQuery Deparam - v0.1.0 - 6/14/2011
// http://benalman.com/
// Copyright (c) 2011 Ben Alman; Licensed MIT, GPL

(function($) {
  // Creating an internal undef value is safer than using undefined, in case it
  // was ever overwritten.
  var undef;
  // A handy reference.
  var decode = decodeURIComponent;

  // Document $.deparam.
  var deparam = $.deparam = function(text, reviver) {
    // The object to be returned.
    var result = {};
    // Iterate over all key=value pairs.
    $.each(text.replace(/\+/g, ' ').split('&'), function(index, pair) {
      // The key=value pair.
      var kv = [pair.substring(0, pair.indexOf('=')), pair.substring(pair.indexOf('=') + 1)]
      // The key, URI-decoded.
      var key = decode(kv[0]);
      // Abort if there's no key.
      if ( !key ) { return; }
      // The value, URI-decoded. If value is missing, use empty string.
      var value = decode(kv[1] || '');
      // If key is more complex than 'foo', like 'a[]' or 'a[b][c]', split it
      // into its component parts.
      var keys = key.split('][');
      var last = keys.length - 1;
      // Used when key is complex.
      var i = 0;
      var current = result;

      // If the first keys part contains [ and the last ends with ], then []
      // are correctly balanced.
      if ( keys[0].indexOf('[') >= 0 && /\]$/.test(keys[last]) ) {
        // Remove the trailing ] from the last keys part.
        keys[last] = keys[last].replace(/\]$/, '');
        // Split first keys part into two parts on the [ and add them back onto
        // the beginning of the keys array.
        keys = keys.shift().split('[').concat(keys);
        // Since a key part was added, increment last.
        last++;
      } else {
        // Basic 'foo' style key.
        last = 0;
      }

      if ( $.isFunction(reviver) ) {
        // If a reviver function was passed, use that function.
        value = reviver(key, value);
      } else if ( reviver ) {
        // If true was passed, use the built-in $.deparam.reviver function.
        value = deparam.reviver(key, value);
      }

      if ( last ) {
        // Complex key, like 'a[]' or 'a[b][c]'. At this point, the keys array
        // might look like ['a', ''] (array) or ['a', 'b', 'c'] (object).
        for ( ; i <= last; i++ ) {
          // If the current key part was specified, use that value as the array
          // index or object key. If omitted, assume an array and use the
          // array's length (effectively an array push).
          key = keys[i] !== '' ? keys[i] : current.length;
          if ( i < last ) {
            // If not the last key part, update the reference to the current
            // object/array, creating it if it doesn't already exist AND there's
            // a next key. If the next key is non-numeric and not empty string,
            // create an object, otherwise create an array.
            current = current[key] = current[key] || (isNaN(keys[i + 1]) ? {} : []);
          } else {
            // If the last key part, set the value.
            current[key] = value;
          }
        }
      } else {
        // Simple key.
        if ( $.isArray(result[key]) ) {
          // If the key already exists, and is an array, push the new value onto
          // the array.
          result[key].push(value);
        } else if ( key in result ) {
          // If the key already exists, and is NOT an array, turn it into an
          // array, pushing the new value onto it.
          result[key] = [result[key], value];
        } else {
          // Otherwise, just set the value.
          result[key] = value;
        }
      }
    });

    return result;
  };

  // Default reviver function, used when true is passed as the second argument
  // to $.deparam. Don't like it? Pass your own!
  deparam.reviver = function(key, value) {
    var specials = {
      'true': true,
      'false': false,
      'null': null,
      'undefined': undef
    };

    return (+value + '') === value ? +value // Number
      : value in specials ? specials[value] // true, false, null, undefined
      : value; // String
  };

}(jQuery));
;
/** ManageSubscriptions is a library which controls all of the behavior of the ManageSubscription.ascx page */
var ManageSubscriptions = function(laneKey, dashType, defaultRecipientEmail, localizedStringDict) {
    /** A unique id to prefix the html elements within the ManageSubscriptions.ascx page.
        It also tells us which lane to get subscriptions for. Ideally, these would be separate. */
    this.laneKey = laneKey;
    /** dashType of the lane to get data for */
    this.dashType = dashType;
    /** The default email address in the "Add new subscription" form */
    this.defaultRecipientEmail = defaultRecipientEmail;

    /** If adding a new subscription, use this as its base search DTO */
    this.searchDTO = EdelweissAnalytics.filterOptions[dashType];

    /** Allows "this" for ManageSubscriptions to be accessible within the submodules. 
        "ms" is short for "ManageSubscriptions" */
    var ms = this;

    /** Variables to be set in this.initialize() */
    this.defaultMessageSubject = "";

    /** Only general cached jquery selectors should go here!
      * Ones that are specific to components should go in there respective component classes 
      */
    var $manageSubscriptionsContainer = $(".manageSubscriptionsContainer[data-lanekey=" + laneKey + "]");
    var $initialLoadingAnimation = $manageSubscriptionsContainer.find(".initialLoadingAnimation");
    var $manageSubscriptionsHeader = $manageSubscriptionsContainer.find(".manageSubscriptionsHeader");
    var $statusMessage = $manageSubscriptionsContainer.find(".statusMessage");

    this.disableAllActions = function () {
        ms.subscriptionList.disableAllActions();
        ms.editSubscriptionForm.disableAllActions();
    },
    this.enableAllActions = function () {
        ms.subscriptionList.enableAllActions();
        ms.editSubscriptionForm.enableAllActions();
    },

    /** For interactions with elements in the subscriptions count container */
    this.subscriptionsCount = new (function() {
        /** cached jquery selectors */
        this.$subscriptionsCountContainer = $manageSubscriptionsContainer.find(".subscriptionsCountContainer");
        this.$showSubscriptionsTableIcon = this.$subscriptionsCountContainer.find(".showSubscriptionsTableIcon");
        this.$showSubscriptionsTableUpwardsIcon = this.$subscriptionsCountContainer.find(".showSubscriptionsTableUpwardsIcon");
        this.$showSubscriptionsTableDownwardsIcon = this.$subscriptionsCountContainer.find(".showSubscriptionsTableDownwardsIcon");
        this.$subscriptionsCountValue = this.$subscriptionsCountContainer.find(".subscriptionsCountValue");

        this.onShowSubscriptionsTableIconClick = function() {
            var _this = ms.subscriptionsCount;
            if (_this.isShowSubscriptionsTableIconPointingUpwards()) {
                _this.makeShowSubscriptionsTableIconPointDownwards();
                ms.subscriptionList.$subscriptionsTable.hide();
                ms.editSubscriptionForm.populateAddNewSubscriptionForm();
                ms.editSubscriptionForm.$editSubscriptionForm.show();
                $statusMessage.hide();
            } else {
                _this.makeShowSubscriptionsTableIconPointUpwards();
                ms.editSubscriptionForm.$editSubscriptionForm.hide();
                ms.subscriptionList.$subscriptionsTable.show();
                $statusMessage.hide();
            }
        },

        this.initialize = function (numberOfSubscriptions) {
            this.setCount(numberOfSubscriptions);
            this.makeShowSubscriptionsTableIconPointDownwards();
            this.$showSubscriptionsTableIcon.on("click",
                this.onShowSubscriptionsTableIconClick);
            this.$subscriptionsCountContainer.show();
        };

        this.setCount = function(newCount) {
            this.$subscriptionsCountValue.html(newCount);
        };
        this.getCount = function() {
            return parseInt(this.$subscriptionsCountValue.html());
        };
        this.incrementCountBy = function(incrementAmount) {
            var currentCount = this.getCount();
            this.setCount(currentCount + incrementAmount);
        };
        
        this.isShowSubscriptionsTableIconPointingUpwards = function() {
            return this.$showSubscriptionsTableUpwardsIcon.is(":visible");
        };
        this.makeShowSubscriptionsTableIconPointUpwards = function() {
            this.$showSubscriptionsTableDownwardsIcon.hide();
            this.$showSubscriptionsTableUpwardsIcon.show();
        },
        this.makeShowSubscriptionsTableIconPointDownwards = function() {
            this.$showSubscriptionsTableUpwardsIcon.hide();
            this.$showSubscriptionsTableDownwardsIcon.show();
        }
    })();

    /** For interactions with the list of all subscriptions for this particular lane */
    this.subscriptionList = new (function() {
        /** cached jquery selectors */
        this.$subscriptionsTable = $manageSubscriptionsContainer.find(".subscriptionsTable");
        this.$subscriptionList = this.$subscriptionsTable.find("tbody");
        this.$noSubscriptionsMessage = this.$subscriptionList.find(".noSubscriptionsMessage");

        this.onSendSubscriptionButtonClick = function ($sendButton) {
            var _this = this;
            var subscriptionId = $sendButton.parents(".subscriptionTemplate").attr("data-subscriptionid");
            _this.showActionsAnimation(subscriptionId);
            ms.disableAllActions();
            ms.subscriptionsAPI.sendSubscription(subscriptionId).then(function () {
                ms.editSubscriptionForm.showDisappearingMessage(ms.constants.successfulSubscriptionSendMessage, "green");
            }).catch(function () {
                ms.editSubscriptionForm.showDisappearingMessage(ms.constants.unsuccessfulSubscriptionSendMessage, "red");
            }).then(function () { // finally
                _this.hideActionsAnimation(subscriptionId);
                ms.enableAllActions();
            });
        };

        this.onEditSubscriptionButtonClick = function ($editButton) {
            var _this = this;
            var subscriptionId = $editButton.parents(".subscriptionTemplate").attr("data-subscriptionid");
            ms.disableAllActions();
            _this.showActionsAnimation(subscriptionId);
            ms.editSubscriptionForm.transitionFromSubscriptionsTableToEditMode(subscriptionId).then(function() {
                ms.enableAllActions();
                _this.hideActionsAnimation(subscriptionId);
            });
        };

        this.onDeleteSubscriptionButtonClick = function ($deleteButton) {
            var _this = this;
            var subscriptionId = $deleteButton.parents(".subscriptionTemplate").attr("data-subscriptionid");
            _this.showActionsAnimation(subscriptionId);
            ms.disableAllActions();
            ms.subscriptionsAPI.removeSubscription(subscriptionId).then(function () {
                _this.removeSubscription(subscriptionId);
                ms.subscriptionsCount.incrementCountBy(-1);
                if (_this.getNumberOfSubscriptions() === 0) {
                    _this.$noSubscriptionsMessage.show();
                }
                _this.hideActionsAnimation(subscriptionId);
                ms.enableAllActions();
            });
        };

        /**
         * @param {object[]} subscriptionsList - List of SubscriptionDTO objects
         */
        this.initialize = function(subscriptionsList) {
            if (subscriptionsList.length > 0) {
                this.addSubscriptions(subscriptionsList);
            } else {
                this.$noSubscriptionsMessage.show();
            }
        };

        /**
         * Turns a list of subscription objects into html which is inserted into the subscriptionList html element
         * @param {object[]} subscriptionList - List of SubscriptionDTO objects
         */
        this.addSubscriptions = function(subscriptionList) {
            var _this = this;
            var subscriptionHtmlList = _.map(subscriptionList, _this.formSubscriptionHtml);
            var htmlResult = _.reduce(subscriptionHtmlList, function (result, subscriptionHtml) {
                return result + subscriptionHtml;
            }, "");
            _this.$subscriptionList.prepend(htmlResult);
        };

        /**
         * Takes a subscription id, gets it from the Subscriptions API, forms it as html, 
         * and either adds or updates it to the subscriptions list.
         */
        this.addOrUpdateSubscription = function(subscriptionId) {
            var _this = this;
            ms.subscriptionsAPI.getSubscription(subscriptionId).then(function (subscription) {
                var subscriptionHtml = _this.formSubscriptionHtml(subscription);
                if (_this.isSubscriptionInList(subscriptionId)) {
                    var $subscriptionTemplate = _this.getSubscriptionTemplateElement(subscriptionId);
                    $subscriptionTemplate.replaceWith(subscriptionHtml);
                } else {
                    if (_this.getNumberOfSubscriptions() === 0) {
                        _this.$noSubscriptionsMessage.hide();
                    }
                    // the most recently created subscriptions should be at the top of the list
                    _this.$subscriptionList.prepend(subscriptionHtml);
                }
            });
        };

        /**
         * Takes a subscription id and removes that subscription from the subscriptionList
         */
        this.removeSubscription = function(subscriptionId) {
            var _this = this;
            if (_this.isSubscriptionInList(subscriptionId)) {
                var $subscriptionTemplate = _this.getSubscriptionTemplateElement(subscriptionId);
                $subscriptionTemplate.remove();
            }
        };

        /**
         * Removes all subscriptions from the subscriptionsList
         */
        this.removeAllSubscriptions = function() {
            _this.$subscriptionList.find(".subscriptionTemplate:not(.originalSubscriptionTemplate)").remove();
        };

        /**
         * Takes a subscription object and transforms it into an html string representing that subscription
         * @param {object} subscription - a SubscriptionDTO object 
         */
        this.formSubscriptionHtml = function(subscription) {
            var searchDTO = JSON.parse(subscription.postBody);

            var subscriptionId = subscription.subscriptionID;
            var messageSubject = subscription.messageSubject; 

            var frequencyType = ms.utilities.getFrequencyTypeFromSubscriptionObject(subscription);
            var frequencyTypeAndValueString;
            if (frequencyType === ms.constants.frequencyType.Daily) {
                frequencyTypeAndValueString = ms.utilities.getFrequencyTypeName(ms.constants.frequencyType.Daily);
            } else {
                var frequencyValue = ms.utilities.getFrequencyValueFromSubscriptionObject(subscription, frequencyType);
                frequencyTypeAndValueString = ms.utilities.getFrequencyTypeAndValueString(frequencyType, frequencyValue);
            }

            var $newSubscription = $manageSubscriptionsContainer.find(".originalSubscriptionTemplate").clone()
                .removeClass("originalSubscriptionTemplate")
                .attr("data-subscriptionid", subscriptionId);

            $newSubscription.find(".messageSubject div").html(messageSubject);
            $newSubscription.find(".messageSubject").attr("title", messageSubject);
            $newSubscription.find(".frequencyTypeAndValue").html(frequencyTypeAndValueString);
            return $newSubscription[0].outerHTML;
        };

        this.disableAllActions = function() {
            $manageSubscriptionsContainer.find(".sendSubscriptionButton").css("pointer-events", "none");
            $manageSubscriptionsContainer.find(".editSubscriptionButton").css("pointer-events", "none");
            $manageSubscriptionsContainer.find(".deleteSubscriptionButton").css("pointer-events", "none");
        };

        this.enableAllActions = function() {
            $manageSubscriptionsContainer.find(".sendSubscriptionButton").css("pointer-events", "auto");
            $manageSubscriptionsContainer.find(".editSubscriptionButton").css("pointer-events", "auto");
            $manageSubscriptionsContainer.find(".deleteSubscriptionButton").css("pointer-events", "auto");
        };

        /**
         * Gets the number of subscriptions currently in the subscriptionList
         */
        this.getNumberOfSubscriptions = function() {
            return this.$subscriptionList.find(".subscriptionTemplate:visible").length;
        };

        this.isSubscriptionInList =function(subscriptionId) {
            var $subscriptionTemplate = this.getSubscriptionTemplateElement(subscriptionId);
            return $subscriptionTemplate.length;
        };

        this.showActionsAnimation = function(subscriptionId) {
            var $subscriptionTemplate = this.getSubscriptionTemplateElement(subscriptionId);
            $subscriptionTemplate.find(".actionsAnimation").show();
        };
        this.hideActionsAnimation = function(subscriptionId) {
            var $subscriptionTemplate = this.getSubscriptionTemplateElement(subscriptionId);
            $subscriptionTemplate.find(".actionsAnimation").hide();
        };

        this.getSubscriptionTemplateElement = function (subscriptionId) {
            var $subscriptionTemplate = this.$subscriptionList.find(".subscriptionTemplate[data-subscriptionid=" + subscriptionId + "]");
            return $subscriptionTemplate;
        };
    })();

    /**  For interactions with the form for adding a new subscription or editing an existing one */
    this.editSubscriptionForm = new (function() {
        /** cached jquery selectors */
        this.$editSubscriptionForm = $manageSubscriptionsContainer.find(".editSubscriptionForm");
        this.$numberOfTitlesSelector = this.$editSubscriptionForm.find(".numberOfTitlesSelector");
        this.$frequencyTypeSelector = this.$editSubscriptionForm.find(".frequencyTypeSelector");
        this.$frequencyValueSelector = this.$editSubscriptionForm.find(".frequencyValueSelector");
        this.$savingAnimation = this.$editSubscriptionForm.find(".savingAnimation");

        this.populateAddNewSubscriptionForm = function() {
            var _this = this;
            var initialFormFieldValues = {
                subscriptionId: ms.constants.outOfRangeSubscriptionId,
                toAddressValue: ms.defaultRecipientEmail,
                messageSubject: ms.defaultMessageSubject,
                frequencyType: ms.constants.frequencyType.Daily,
                frequencyValue: null,
                numberOfTitles: ms.constants.defaultNumberOfTitles
            };
            _this.setCurrentFormFieldValues(initialFormFieldValues);
        };

        this.populateEditExistingSubscriptionForm = function(subscriptionId) {
            var _this = this;
            var donePromise = new Promise(function (resolve, reject) {
                ms.subscriptionsAPI.getSubscription(subscriptionId).then(function (subscription) {
                    var frequencyType = ms.utilities.getFrequencyTypeFromSubscriptionObject(subscription);
                    var frequencyValue = ms.utilities.getFrequencyValueFromSubscriptionObject(subscription, frequencyType);

                    var numberOfTitles;
                    var searchDTO = JSON.parse(subscription.postBody);
                    if (searchDTO.hasOwnProperty("numberRequestedForEmail")) {
                        numberOfTitles = searchDTO.numberRequestedForEmail;
                    } else {
                        numberOfTitles = ms.constants.defaultNumberOfTitles;
                    }

                    var initialFormFieldValues = {
                        subscriptionId: subscriptionId,
                        toAddressValue: subscription.recipientEmail,
                        messageSubject: subscription.messageSubject,
                        frequencyType: frequencyType,
                        frequencyValue: frequencyValue,
                        numberOfTitles: numberOfTitles
                    };
                    _this.setCurrentFormFieldValues(initialFormFieldValues); 
                    resolve();
                });
            });
            return donePromise;
        };

        this.setFormHeader = function(headerName) {
            this.$editSubscriptionForm.find(".editSubscriptionFormHeader").html(headerName);
        };

        /**
         * Populates the frequencyValueSelector element with the default options for frequency value
         */
        this.initializeFrequencyValueSelector = function(frequencyType) {
            var _this = this;
            var optionsHtml = "";
            if (frequencyType === ms.constants.frequencyType.Weekly) {
                for (var i = 0; i < ms.constants.namesOfDaysOfWeek.length; i++) {
                    optionsHtml += ms.utilities.getOptionHtml(ms.constants.namesOfDaysOfWeek[i], i);
                }
                _this.setFrequencyValueSelectorLabel(frequencyType);
            } else if (frequencyType === ms.constants.frequencyType.Monthly) {
                for (var i = 1; i <= 31; i++) {
                    optionsHtml += ms.utilities.getOptionHtml(i, i);
                }
                _this.setFrequencyValueSelectorLabel(frequencyType);
            }
            
            _this.$frequencyValueSelector.empty().append(optionsHtml);
        };

        /**
         * Populates the frequencyTypeSelector element with the default options for frequency type
         */
        this.initializeFrequencyTypeSelector = function() {
            var _this = this;
            var optionsHtml = "";
            for (var i = 0; i < ms.constants.namesOfFrequencyTypes.length; i++) {
                optionsHtml += ms.utilities.getOptionHtml(ms.constants.namesOfFrequencyTypes[i], i);
            }

            _this.$frequencyTypeSelector.empty().append(optionsHtml);

            _this.$frequencyTypeSelector.on("change", function () { 
                var frequencyType = _this.getSelectedFrequencyType();
                if (frequencyType === ms.constants.frequencyType.Daily) {
                    _this.hideFrequencyValueSelector();
                } else {
                    _this.initializeFrequencyValueSelector(frequencyType);
                    _this.showFrequencyValueSelector();
                }
            });
        };

        /**
         * Populates the numberOfTitlesSelector element with the default options for number of titles
         */
        this.initializeNumberOfTitlesSelector = function() {
            var optionsHtml = "";
            for (var i = 0; i < ms.constants.numberOfTitlesOptions.length; i++) {
                var numberOfTitles = ms.constants.numberOfTitlesOptions[i];
                optionsHtml += ms.utilities.getOptionHtml(numberOfTitles.toString(), numberOfTitles);
            }
            this.$numberOfTitlesSelector.empty().append(optionsHtml);
        };

        /**
         * Returns whether the form is being used for adding or updating a subscription
         * @returns {Boolean} isAddingNewSubscription - true -> adding, false -> updating
         */
        this.isAddingASubscription = function() {
            var subscriptionId = this.$editSubscriptionForm.attr("data-subscriptionid");
            return subscriptionId === ms.constants.outOfRangeSubscriptionId;
        };

        this.showDisappearingMessage = function(messageString, textColor) {
            var _this = this;
            $statusMessage.html(messageString)
                .css("color", textColor).show();
            setTimeout(function () {
                $statusMessage.fadeOut().empty();
            }, 10000);
        };

        /**
         * Sets the inputs in the form with values
         */
        this.setCurrentFormFieldValues = function(formFieldValues) {
            var _this = this;
            _this.$editSubscriptionForm.attr("data-subscriptionid", formFieldValues.subscriptionId);
            _this.$editSubscriptionForm.find(".toAddressValue").val(formFieldValues.toAddressValue);
            _this.$editSubscriptionForm.find(".messageSubjectValue").val(formFieldValues.messageSubject);

            _this.initializeNumberOfTitlesSelector();
            _this.setSelectedNumberOfTitles(formFieldValues.numberOfTitles);
            
            _this.initializeFrequencyTypeSelector();
            _this.setSelectedFrequencyType(formFieldValues.frequencyType);

            if (formFieldValues.frequencyType === ms.constants.frequencyType.Daily) {
                _this.hideFrequencyValueSelector();
            } else {
                _this.initializeFrequencyValueSelector(formFieldValues.frequencyType);
                _this.setSelectedFrequencyValue(formFieldValues.frequencyValue);
                _this.showFrequencyValueSelector();
            }
        };

        /**
         * Gets the values the user has provided in the form
         */
        this.getCurrentFormFieldValues = function() {
            var _this = this;
            var currentFormFieldValues = {};

            var dayOfWeek = ms.utilities.getCurrentDayOfWeek();
            var dayOfMonth = ms.utilities.getCurrentDayOfMonth();
            var frequencyType = _this.getSelectedFrequencyType(); 
            if (frequencyType === ms.constants.frequencyType.Weekly) {
                dayOfWeek = _this.getSelectedFrequencyValue();
            } else if (frequencyType === ms.constants.frequencyType.Monthly) {
                dayOfMonth = _this.getSelectedFrequencyValue();
            }

            var toAddress = _this.getSelectedToAddress();
            var messageSubject = _this.getSelectedMessageSubject();
            var numberOfTitles = _this.getSelectedNumberOfTitles();

            var currentFormFieldValues = {
                dayOfWeek: dayOfWeek,
                dayOfMonth: dayOfMonth,
                frequency: frequencyType,
                toAddress: toAddress,
                messageSubject: messageSubject,
                numberOfTitles: numberOfTitles
            };
            return currentFormFieldValues;
        };

        this.onSaveButtonClick = function($saveButton) {
            var _this = this;
            ms.disableAllActions();
            _this.$savingAnimation.show();
            $statusMessage.hide();
            // delay to prevent spamming of save button and reduce server stress
            setTimeout(function () {
                var subscriptionId = _this.$editSubscriptionForm.attr("data-subscriptionid");
                var currentFormFieldValues = _this.getCurrentFormFieldValues();
                if (_this.isAddingASubscription()) {
                    ms.subscriptionsAPI.addSubscription(currentFormFieldValues).then(function (subscriptionId) {
                        ms.subscriptionList.addOrUpdateSubscription(subscriptionId);
                        ms.subscriptionsCount.incrementCountBy(1);
                        _this.$savingAnimation.hide();
                        ms.enableAllActions();
                        _this.populateAddNewSubscriptionForm();
                        _this.showDisappearingMessage(ms.constants.successfulSubscriptionSaveMessage, "green");
                    }).catch(function () {
                        ms.editSubscriptionForm.showDisappearingMessage(ms.constants.unsuccessfulSubscriptionSaveMessage, "red");
                    }).then(function () {
                        _this.$savingAnimation.hide();
                    });
                } else {
                    ms.subscriptionsAPI.saveSubscriptionEdits(subscriptionId, currentFormFieldValues).then(function () {
                        ms.subscriptionList.addOrUpdateSubscription(subscriptionId);
                        _this.$savingAnimation.hide();
                        ms.enableAllActions();
                        _this.transitionFromEditModeToSubscriptionsTable(subscriptionId);
                    });
                }
            }, 2000);
        };

        this.onCancelButtonClick = function($cancelButton) {
            var _this = this;
            var subscriptionId = _this.$editSubscriptionForm.attr("data-subscriptionid");
            _this.transitionFromEditModeToSubscriptionsTable(subscriptionId);
        };

        /**
         * Transition the from being used for adding to editing
         */
        this.transitionFromSubscriptionsTableToEditMode = function(subscriptionId) {
            var _this = this;
            var donePromise = new Promise(function (resolve, reject) {
                _this.showCancelEditingSubscriptionButton();
                _this.setFormHeader(ms.constants.editSubscriptionFormHeader);
                _this.populateEditExistingSubscriptionForm(subscriptionId).then(function () {
                    _this.$editSubscriptionForm.show();
                    ms.subscriptionList.$subscriptionsTable.hide();
                    ms.subscriptionsCount.$subscriptionsCountContainer.hide();
                    resolve();
                });
            });
            return donePromise;
        };

        /**
         * Transition the form from being used for editing to adding
         */
        this.transitionFromEditModeToSubscriptionsTable = function(subscriptionId) {
            var _this = this;
            _this.$editSubscriptionForm.hide();
            ms.subscriptionList.$subscriptionsTable.show();
            ms.subscriptionsCount.$subscriptionsCountContainer.show();
            _this.hideCancelEditingSubscriptionButton();
            _this.setFormHeader(ms.constants.addNewSubscriptionFormHeader);
            _this.populateAddNewSubscriptionForm();
            ms.subscriptionList.enableAllActions();
        };

        /**
         * Set the initial state of the form to be for adding a new subscription
         */
        this.initializeInAddMode =function() {
            this.hideCancelEditingSubscriptionButton();
            this.setFormHeader(ms.constants.addNewSubscriptionFormHeader);
            this.populateAddNewSubscriptionForm();
            this.$editSubscriptionForm.show();
        };

        this.setSelectedFrequencyType =function(frequencyType) {
            this.$frequencyTypeSelector.val(frequencyType);
        };
        this.getSelectedFrequencyType = function() {
            var frequencyType = this.$frequencyTypeSelector.val();
            return (frequencyType === null || typeof frequencyType !== "undefined") ? parseInt(frequencyType) : null;
        };

        this.setSelectedFrequencyValue = function(frequencyValue) {
            this.$frequencyValueSelector.val(frequencyValue);
        };
        this.getSelectedFrequencyValue = function() {
            var frequencyValue = this.$frequencyValueSelector.val();
            return (frequencyValue === null || typeof frequencyValue !== "undefined") ? parseInt(frequencyValue) : null;
        };
       
        this.setSelectedNumberOfTitles = function(numberOfTitles) {
            this.$numberOfTitlesSelector.val(numberOfTitles);
        };
        this.getSelectedNumberOfTitles = function() {
            var numberOfTitles = this.$numberOfTitlesSelector.val();
            return (numberOfTitles === null || typeof numberOfTitles !== "undefined") ? parseInt(numberOfTitles) : null;
        };

        this.hideFrequencyValueSelector = function() {
            this.$editSubscriptionForm.find(".frequencyValueSelectorContainer").hide();
        };
        this.showFrequencyValueSelector = function() {
            this.$editSubscriptionForm.find(".frequencyValueSelectorContainer").show();
        };
        this.setFrequencyValueSelectorLabel = function(frequencyType) {
            var frequencyValueLabel = ms.utilities.getFrequencyValueLabel(frequencyType);
            this.$editSubscriptionForm.find(".frequencyValueSelectorLabel").html(frequencyValueLabel);
        };

        this.getSelectedToAddress = function() {
            var toAddress = this.$editSubscriptionForm.find(".toAddressValue").val();
            return toAddress;
        };
        this.getSelectedMessageSubject = function() {
            var messageSubject = this.$editSubscriptionForm.find(".messageSubjectValue").val();
            return messageSubject;
        };

        this.showCancelEditingSubscriptionButton = function() {
            this.$editSubscriptionForm.find(".cancelEditingSubscriptionButton").show();
        };
        this.hideCancelEditingSubscriptionButton = function() {
            this.$editSubscriptionForm.find(".cancelEditingSubscriptionButton").hide();
        };

        this.disableAllActions = function () {
            this.$editSubscriptionForm.find(".saveSubscriptionEditsButton").prop("disabled", true);
            this.$editSubscriptionForm.find(".cancelEditingSubscriptionButton").prop("disabled", true);
        };

        this.enableAllActions = function () {
            this.$editSubscriptionForm.find(".saveSubscriptionEditsButton").prop("disabled", false);
            this.$editSubscriptionForm.find(".cancelEditingSubscriptionButton").prop("disabled", false);
        };
    })();

    /** For interactions with the Subscriptions API (SubscriptionsController.cs) */
    this.subscriptionsAPI = new (function() {
        /**
         * Gets the list of subscriptions objects for a lane
         * @returns {Promise} subscriptionsPromise - yields a list of subscription objects
         */
        this.getSubscriptions = function() {
            var subscriptionsPromise = new Promise(function (resolve, reject) {
                $.ajax({
                    url: "/api/v2/subscriptions/lane/" + ms.laneKey,
                    type: "GET",
                    success: function (subscriptions) {
                        resolve(subscriptions);
                    },
                    error: function (err) {
                        reject("There was an error in getting the subscriptions for this lane.");
                    }
                });
            });
            return subscriptionsPromise;
        };

        /**
         * Gets a subscription object
         * @param {int} subscriptionId
         * @returns {Promise} subscriptionPromise - yields the subscription object
         */
        this.getSubscription = function(subscriptionId) {
            var subscriptionPromise = new Promise(function (resolve, reject) {
                $.ajax({
                    type: "GET",
                    url: "/api/v1/subscriptions/" + subscriptionId,
                    success: function (subscription) {
                        resolve(subscription);
                    },
                    error: function (err) {
                        reject("There was an error in getting the data for this subscriptions.");
                    }
                });
            });
            return subscriptionPromise;
        };

        /**
         * Adds a subscription for this lane
         * @param {object} currentFormFieldValues
         * @returns {Promise} - yields the id of the newly added subscription
         */
        this.addSubscription = function(currentFormFieldValues) {
            var subscription = ms.utilities.formSubscriptionDTO(currentFormFieldValues, ms.searchDTO, 0);
            var donePromise = new Promise(function (resolve, reject) {
                $.ajax({
                    type: "POST",
                    url: "/api/v2/subscriptions/lane/" + ms.laneKey,
                    data: subscription,
                    success: function (subscriptionId) {
                        resolve(subscriptionId); 
                    },
                    error: function (err) {
                        reject("There was an error in adding a new subscription for this lane.");
                    }
                });
            });
            return donePromise;
        };

        /**
         * Unsubscribe from all subscriptions for this lane
         * @returns {Promise} donePromise - signals all subscriptions have been removed (when yielded)
         */
        this.removeAllSubscriptions = function() {
            var donePromise = new Promise(function (resolve, reject) {
                $.ajax({
                    type: "DELETE",
                    url: "/api/v1/subscriptions/lane/unsubscribe/" + ms.laneKey,
                    success: function (response) {
                        resolve(); 
                    },
                    error: function (err) {
                        reject("There was an error in unsubscribing from this lane.");
                    }
                });
            });
            return donePromise;
        };

        /**
         * Unsubscribe from a specific subscription
         * @param {number} subscriptionId - the id of the subscription to delete 
         * @param {Promise} donePromise - signals the subscription has removed (when yielded) 
         */
        this.removeSubscription = function(subscriptionId) {
            var donePromise = new Promise(function (resolve, reject) {
                $.ajax({
                    type: "DELETE",
                    url: "/api/v1/subscriptions/" + subscriptionId,
                    success: function (response) {
                        resolve(); 
                    },
                    error: function (err) {
                        reject("There was an error in unsubscribing from this subscription.");
                    }
                });
            });
            return donePromise;
        };

        /**
         * Updates the specified subscription with the new form field values
         * @param {int} subscriptionId - the subscription to be edited
         * @param {object} currentFormFieldValues - the edited values to update the subscription with
         * @returns {Promise} donePromise - signals the updates have occurred (when yielded) 
         */
        this.saveSubscriptionEdits = function(subscriptionId, currentFormFieldValues) {
            var donePromise = new Promise(function (resolve, reject) {
                ms.subscriptionsAPI.getSubscription(subscriptionId).then(function (subscription) {
                    var searchDTO = JSON.parse(subscription.postBody);
                    var subscription = ms.utilities.formSubscriptionDTO(currentFormFieldValues, searchDTO, subscriptionId);
                    $.ajax({
                        type: "POST",
                        url: "/api/v1/subscriptions",
                        data: subscription,
                        dataType: "json",
                        success: function (response) {
                            resolve(); 
                        },
                        error: function (err) {
                            reject("There was an error in updating this subscription.");
                        }
                    });
                });
            });
            return donePromise;
        };

        /**
         * Sends a test email of the subscription content to the user
         * @param {int} subscriptionId - the subscription for which an email is sent
         * @returns {Promise} donePromise - signals the test email has been sent (when yielded) 
         */
        this.sendSubscription = function (subscriptionId) {
            var donePromise = new Promise(function (resolve, reject) {
                $.ajax({
                    type: "GET",
                    url: "/api/v1/subscriptions/send/subscription/" + subscriptionId,
                    success: function (response) {
                        resolve();
                    },
                    error: function (err) {
                        reject("There was an error in sending a test email for this subscription.");
                    }
                });
            });
            return donePromise;
        };
    })();

    /** General functions usable everywhere in ManageSubscriptions.js */
    this.utilities = new (function() {
        this.getCurrentDayOfWeek =function() {
            return new Date().getDay();
        };

        this.getCurrentDayOfMonth = function() {
            return new Date().getDate();
        };

        this.getOptionHtml = function (name, value) {
            return "<option value='" + value + "'>" + name + "</option>";
        };

        this.getFrequencyValueFromSubscriptionObject = function(subscription, frequencyType) {
            if (frequencyType === ms.constants.frequencyType.Weekly) {
                return subscription.dayOfWeek;
            } else if (frequencyType === ms.constants.frequencyType.Monthly) {
                return subscription.dayOfMonth;
            } else {
                return null;
            }
        };

        /**
         * Converts an integer frequency type into an interpretable string for frequency type
         * Example: 0 -> "Daily"
         */
        this.getFrequencyTypeName = function(frequencyType) {
            if (frequencyType >= 0 && frequencyType < ms.constants.namesOfFrequencyTypes.length) {
                return ms.constants.namesOfFrequencyTypes[frequencyType];
            } else {
                console.error("Invalid frequency type. Frequency type name cannot be retrieved.");
                return null;
            }
        };

        this.getFrequencyTypeFromSubscriptionObject = function(subscription) {
            // The subscription object stores frequency as an english string
            // These frequencyTypeNames should not be localized
            var frequencyTypeName = subscription.frequency;
            if (frequencyTypeName === "Daily") {
                return ms.constants.frequencyType.Daily;
            } else if (frequencyTypeName === "Weekly") {
                return ms.constants.frequencyType.Weekly;
            } else if (frequencyTypeName === "Monthly") {
                return ms.constants.frequencyType.Monthly;
            } else {
                console.error("Invalid frequency type name. Frequency type cannot be retrieved.");
                return null;
            }
        };

        /**
         * Converts an integer frequency value into an interpretable string
         * Example: 0 -> "Sunday"
         */
        this.getFrequencyValueName = function(frequencyType, frequencyValue) {
            if (frequencyType === ms.constants.frequencyType.Weekly) {
                return ms.constants.namesOfDaysOfWeek[frequencyValue];
            } else if (frequencyType === ms.constants.frequencyType.Monthly) {
                return frequencyValue;
            } else {
                console.error("Invalid frequency type. Frequency value name cannot be retrieved.");
                return null;
            }
        };

        /**
         * Converts an integer frequency type into an interpretable label for frequency values
         * Ex: 1 - > "Day Of Week"
         */
        this.getFrequencyValueLabel = function(frequencyType) {
            if (frequencyType === ms.constants.frequencyType.Weekly) {
                return ms.constants.dayOfWeekString;
            } else if (frequencyType === ms.constants.frequencyType.Monthly) {
                return ms.constants.dayOfMonthString;
            } else {
                console.error("Invalid frequency type. Frequency value label cannot be retrieved.");
                return null;
            }
        };

        /**
         * Combines frequency type (ex: Monthly) and frequency value (ex: 1) into a string representing both of them (ex: "Monthly, 1st")
         */
        this.getFrequencyTypeAndValueString = function(frequencyType, frequencyValue) {
            if (frequencyType === ms.constants.frequencyType.Daily) {
                // "Daily"
                var frequencyTypeName = this.getFrequencyTypeName(frequencyType);
                return frequencyTypeName;
            } else if (frequencyType === ms.constants.frequencyType.Weekly) {
                // "Weekly, Sunday"
                var frequencyTypeName = this.getFrequencyTypeName(frequencyType);
                var frequencyValueName = this.getFrequencyValueName(frequencyType, frequencyValue);
                return frequencyTypeName + ", " + frequencyValueName;
            } else if (frequencyType === ms.constants.frequencyType.Monthly) {
                // "Monthly, 1st"
                var frequencyTypeName = this.getFrequencyTypeName(frequencyType);
                var frequencyValueName = this.getFrequencyValueName(frequencyType, frequencyValue);
                var ordinalSuffix = this.getOrdinalSuffix(frequencyValue);
                return frequencyTypeName + ", " + frequencyValueName + ordinalSuffix;
            } else {
                console.error("Invalid frequency type. Frequency type and value string cannot be retrieved.");
                return null;
            }
        };

        /**
         * Gets the ordinal suffix for a natural number
         * Source: https://stackoverflow.com/a/13627586
         */
        this.getOrdinalSuffix = function(i) {
            var j = i % 10, k = i % 100;
            if (j == 1 && k != 11) return "st";
            if (j == 2 && k != 12) return "nd";
            if (j == 3 && k != 13) return "rd";
            return "th";
        };

        /**
         * Forms a subscriptionDTO object for this subscription
         * @param {object} currentFormFieldValues - the values in the Add/Edit subscription form
         * @param {object} searchDTO - the search DTO for this subscription
         * @param {int} subscriptionId
         */
        this.formSubscriptionDTO = function (currentFormFieldValues, searchDTO, subscriptionId) {
            searchDTO.numberRequestedForEmail = currentFormFieldValues.numberOfTitles;
            var subscription = $.extend(ms.constants.baseSubscription, {
                PostBody: JSON.stringify(searchDTO),
                Frequency: ms.utilities.getFrequencyTypeName(currentFormFieldValues.frequency),
                DayOfWeek: currentFormFieldValues.dayOfWeek,
                DayOfMonth: currentFormFieldValues.dayOfMonth,
                RecipientEmail: currentFormFieldValues.toAddress,
                MessageSubject: currentFormFieldValues.messageSubject,
                SubscriptionID: subscriptionId
            });
            return subscription;
        };
    })();

    /** Container of constants */
    this.constants = new (function() {
        /** Localized string constants */
        this.namesOfDaysOfWeek = localizedStringDict.namesOfDaysOfWeek;
        this.namesOfFrequencyTypes = localizedStringDict.namesOfFrequencyTypes;
        this.successfulSubscriptionSaveMessage = localizedStringDict.successfulSubscriptionSaveMessage;
        this.unsuccessfulSubscriptionSaveMessage = localizedStringDict.unsuccessfulSubscriptionSaveMessage;
        this.successfulSubscriptionSendMessage = localizedStringDict.successfulSubscriptionSendMessage;
        this.unsuccessfulSubscriptionSendMessage = localizedStringDict.unsuccessfulSubscriptionSendMessage;
        this.addNewSubscriptionFormHeader = localizedStringDict.addNewSubscriptionFormHeader;
        this.editSubscriptionFormHeader = localizedStringDict.editSubscriptionFormHeader;
        this.manageSubscriptionsHeader = localizedStringDict.manageSubscriptionsHeader;
        this.dayOfWeekString = localizedStringDict.dayOfWeekString;
        this.dayOfMonthString = localizedStringDict.dayOfMonthString;

        /** Numeric and data-related constants */
        this.frequencyType = {
            Daily: 0,
            Weekly: 1,
            Monthly: 2
        };

        this.numberOfTitlesOptions = [10, 25, 50];
        this.defaultNumberOfTitles = 25;
        this.outOfRangeSubscriptionId = "-1";
        this.baseSubscription = {
            Uri: window.location.origin + "/api/v2/subscriptions/lane/content/" + ms.laneKey,
            Method: "POST"
        };
    })();

    this.initialize = function () {
        $initialLoadingAnimation.show();
        async.parallel({
            initialSubscriptions: getInitialSubscriptions,
            manageSubscriptionsHeader: getManageSubscriptionsHeader,
            defaultMessageSubject: getDefaultMessageSubject 
        }, function(err, results) {
            // short delay added to prevent loading animation flicker
            setTimeout(function () {
                $initialLoadingAnimation.hide();  
                ms.defaultMessageSubject = results.defaultMessageSubject;
                ms.subscriptionsCount.initialize(results.initialSubscriptions.length);
                ms.subscriptionList.initialize(results.initialSubscriptions); 
                ms.editSubscriptionForm.initializeInAddMode();
                $manageSubscriptionsHeader.html(results.manageSubscriptionsHeader);
            }, 500);
        });
    };

    /** private functions used within initialize() */
    function getInitialSubscriptions(callback) {
        ms.subscriptionsAPI.getSubscriptions().then(function (subscriptions) {
            callback(null, subscriptions);
        });
    };
    function getManageSubscriptionsHeader(callback) {
        EdelweissAnalytics.getLane(ms.laneKey).then(function (lane) {
            var laneTitleHtml = "<span class='accFont'>" + lane.header.title + "</span>"
            var manageSubscriptionsHeader = ms.constants.manageSubscriptionsHeader.replace("{0}", laneTitleHtml);
            callback(null, manageSubscriptionsHeader);
        });
    };
    function getDefaultMessageSubject(callback) {
        EdelweissAnalytics.getLane(ms.laneKey).then(function (lane) {
            var defaultMessageSubject = lane.subscription.reportName;
            callback(null, defaultMessageSubject);
        });
    }
};

/**
 * Binds the ManageSubscriptions.ascx form as a popover on top of a specific html target
 * @param {string} target - a jQuery selector string for the popover target
 * @param {number} laneKey - the id of the lane
 * @param {number} dashType - the dashType of the lane
 */
ManageSubscriptions.bindManageSubscriptionsFormAsPopover = function(target, laneKey, dashType) {
    var url = "/GetTreelineControl.aspx?controlName=/uc/user/ManageSubscriptions.ascx";
    var params = {
        laneKey: laneKey,
        dashType: dashType
    };
    url += "&" + $.param(params);

    $(target).webuiPopover({
        trigger: "click",
        placement: "bottom-right",
        width: 650,
        type: "async",
        url: url,
        cache: false,
        dismissible: false, // don't allow select boxes to close popover
        closeable: true, // adds fixed close button to upper-right
        multi: true, // allow multiple popovers open at once (title-popover won't close this one)
        onHide: function ($element) {
            // Even with cache:false, html lingers for a second when reopened. Hide it before closing.
            var $manageSubscriptionsContainer = $(".manageSubscriptionsContainer[data-laneKey=" + laneKey + "]");
            $manageSubscriptionsContainer.hide();
        }
    });
};;
EdelweissAnalytics.Exclusions = new (function () {
    //// PUBLIC FUNCTIONS ////

    this.excludedProductIdsType = {
        none: 0,
        sku: 1,
        itemIdAndSku: 2
    };

    this.excludeSkuInLane = function (sku, laneKey, dashType, sliderIndex) {
        addEntityIdsToExclusionListInDatabase(laneKey, [sku]).then(function () {
            hideTitleInSlider(sliderIndex, laneKey);
            hideSliderElementTooltip(dashType, sku);
            increaseExclusionsMessageCount(laneKey);
        }).catch(function (err) {
            console.error(err);
            alert(err);
        });
    };

    this.excludeSkusForWeedingAnalysis = function () {
        if (EdelweissAnalytics.rows.length === 0) {
            modalAlert(getRes("must_select_one_or_more"));
            return;
        }
        var itemList = _.map(EdelweissAnalytics.rows, "item");
        var exclusionSkuList = getSkuListFromItemList(itemList);
        addEntityIdsToExclusionListInDatabase(EdelweissAnalytics.LaneKeys.WeedingAnalysis, exclusionSkuList).then(function () {
            hideItemsInWeedingAnalysisDataTable(exclusionSkuList);
        }).catch(function () {
            console.error(err);
            alert(err);
        });
    };

    this.updateExclusionsMessage = function (excludedSkus, laneKey) {
        var $exclusionsMessageContainer = getExclusionsMessageContainerElement(laneKey);
        var $exclusionsMessage = getExclusionsMessageElement(laneKey);
        var numExcluded = excludedSkus.length;
        if (numExcluded > 0) {
            var messageString = getExclusionsMessage(numExcluded);
            $exclusionsMessage.html(messageString);
            $exclusionsMessage.attr("data-numExcluded", numExcluded);
            $exclusionsMessageContainer.show();
        } else {
            $exclusionsMessage.attr("data-numExcluded", 0);
            $exclusionsMessageContainer.hide();
        }
    };

    this.clearExclusionsForLane = function (laneKey) {
        clearExclusionListInDatabase(laneKey).then(function () {
            EdelweissAnalytics.startLaneUpdateProcess(laneKey);
        }).catch(function (err) {
            console.error(err);
            alert(err);
        });
    };

    this.hideExclusionsMessageContainer = function (laneKey) {
        getExclusionsMessageContainerElement(laneKey).hide();  
    };

    //// PRIVATE FUNCTIONS ////

    var hideSliderElementTooltip = function (dashType, sku) {
        $("#thumb_" + dashType + "_" + sku).webuiPopover('hide');
    }

    var getExclusionsMessageElement = function (laneKey) {
        var $exclusionsMessageElement = $(".analyticsLane[data-lanekey='" + laneKey + "'] "
            + ".analyticsLaneExclusionsMessage");
        return $exclusionsMessageElement;
    };

    var getExclusionsMessageContainerElement = function (laneKey) {
        var $exclusionsMessageContainerElement = $(".analyticsLane[data-lanekey='" + laneKey + "'] "
            + ".analyticsLaneExclusionsMessageContainer");
        return $exclusionsMessageContainerElement;
    };

    var increaseExclusionsMessageCount = function (laneKey) {
        var $exclusionsMessageContainer = getExclusionsMessageContainerElement(laneKey);
        var $exclusionsMessage = getExclusionsMessageElement(laneKey);
        var newNumExcluded = parseInt($exclusionsMessage.attr("data-numExcluded")) + 1;
        var messageString = getExclusionsMessage(newNumExcluded);
        $exclusionsMessage.attr("data-numExcluded", newNumExcluded);
        $exclusionsMessage.html(messageString);
        if (!$exclusionsMessageContainer.is(":visible")) {
            $exclusionsMessageContainer.show();
        }
    };

    var getExclusionsMessage = function (numExcluded) {
        var titlesLabel = numExcluded === 1 ? getRes("title").toLowerCase()
            : getRes("titles").toLowerCase();
        var messageString = numExcluded + " hidden " + titlesLabel;
        return messageString;
    };

    var hideTitleInSlider = function (sliderIndex, laneKey) {
        var sliderId = "detail_" + laneKey + "-infiniteSlider";
        InfiniteSlider.hideSliderElement(sliderId, sliderIndex);
    };

    var hideItemsInWeedingAnalysisDataTable = function (skuList) {
        var table = $("#weedingGrid").DataTable();
        var indexes = table.rows().eq(0).filter( function (rowIdx) {
            var sku = table.row(rowIdx).data().sku;
            return _.includes(skuList, sku); 
        }); 
        table.rows(indexes).remove().draw();
        EdelweissAnalytics.deselectAllTitlesOnAllPages();
    };

    var getSkuListFromItemList = function (itemList) {
        var outputSkuList = [];
        //Assumes sku,item
        for (var i = 0; i < itemList.length; i++) {
            outputSkuList[i] = itemList[i].replace(/,[^$]+/g, '');
        }
        return outputSkuList;
    }

    var addEntityIdsToExclusionListInDatabase = function (laneKey, entityIds) {
        var donePromise = new Promise(function (resolve, reject) {
            $.ajax({
                type: "PUT",
                url: "api/v1/me/analytics/lane/" + laneKey + "/excludedEntityIds",
                data: JSON.stringify(entityIds),
                contentType: "application/json",
                dataType: "json",
                success: function () {
                    resolve();
                },
                error: function (err) {
                    reject(getRes("error_excluding_items"));
                }
            });
        });
        return donePromise;
    };

    var clearExclusionListInDatabase = function (laneKey) {
        var donePromise = new Promise(function (resolve, reject) {
            $.ajax({
                type: "DELETE",
                url: "api/v1/me/analytics/lane/" + laneKey,
                success: function () {
                    resolve();
                },
                error: function (err) {
                    reject(getRes("error_clearing_exclusions_for_lane"));
                }
            });
        });
        return donePromise;
    };
})();;
EdelweissAnalytics.Weeding = new (function () {
    var _this = this;
    this.maxShown = 1000;

    this.profileIdEnum = {
        newSavedProfile: -2,
        activeProfile: -1
        // existing profiles have ids >= 0
    };

    this.weedingParameterId = {
        maxMonthsUncirculated: "maxMonthsUncirculated",
        maxYearsPubDate: "maxYearsPubDate",
        maxYearsInSystem: "maxYearsInSystem",
        maxCirculations: "maxCirculations",
        minCopiesOnHand: "minCopiesOnHand"
    };

    this.mySavedProfileIds = [];

    this.addToMySavedProfileIdsList = function (id) {
        if (!_.includes(this.mySavedProfileIds, id)) {
            this.mySavedProfileIds.push(id);
        }
    };
    this.removeFromMySavedProfileIdsList = function (id) {
        _.pull(this.mySavedProfileIds, id);
    };
    this.isMySavedProfile = function (id) {
        return _.includes(this.mySavedProfileIds, id);
    };

    // weedingProfileId of -2 means adding new saved profile
    // weedingProfileId of -1 means updating active profile
    // weedingProfileId of >= 0 means updating saved profile
    this.addOrUpdateProfile = function (weedingProfileId, weedingProfileJSON) {
        var doneSaving = new Promise(function (resolve, reject) {
            $.ajax({
                url: "api/v1/analysis/weeding/profiles/" + weedingProfileId,
                type: "POST",
                data: weedingProfileJSON,
                success: function (newWeedingProfileId) {
                    resolve(newWeedingProfileId);
                },
                error: function (error) {
                    reject(error);
                }
            });
        });
        return doneSaving;
    };

    this.deleteProfile = function (weedingProfileId) {
        var doneDeleting = new Promise(function (resolve, reject) {
            $.ajax({
                url: "api/v1/analysis/weeding/profiles/" + weedingProfileId,
                type: "DELETE",
                success: function () {
                    resolve();
                },
                error: function () {
                    reject();
                }
            });
        });
        return doneDeleting;
    };

    this.saveProfileSelection = function (profileId) {
        var doneSavingPromise = new Promise(function (resolve, reject) {
            $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", {
                type: "analytics",
                name: "wda.selectedWeedingProfileId",
                value: profileId
            }).done(function () {
                resolve();
            }).fail(function () {
                reject();
            });
        });
        return doneSavingPromise;
    };

    this.getWeedingProfile = function (weedingProfileId) {
        var doneRetrievingPromise = new Promise(function (resolve, reject) {
            $.ajax({
                url: "api/v1/analysis/weeding/profiles/" + weedingProfileId,
                type: "GET",
                success: function (weedingProfileJSON) {
                    resolve(weedingProfileJSON);
                },
                error: function (error) {
                    reject(error);
                }
            });
        });
        return doneRetrievingPromise;
    };

    this.openAddOrUpdateProfileModal = function (weedingProfileId) {
        var url = "/GetTreelineControl.aspx?controlName=/uc/analytics/AddOrUpdateWeedingProfile.ascx";
        url += "&" + $.param({
            weedingProfileId: weedingProfileId,
            isPaddingAroundFormIncluded: true
        });
        openModal(url, "825px", "300px");
    };

    this.showSelectedWeedingSettings = function (selectedWeedingProfileId) {
        var doneShowingPromsie = new Promise(function (resolve, reject) {
            var params = {
                weedingProfileId: selectedWeedingProfileId
            };
            var url = "/GetTreelineControl.aspx?controlName=/uc/analytics/SelectedWeedingSettings.ascx"
                + "&" + $.param(params);
            $("#selectedWeedingSettings").load(url, function (response, status, xhr) {
                if (status == "error") {
                    reject(xhr.status + " " + xhr.statusText);
                } else {
                    resolve();
                }
            });
        });
        return doneShowingPromsie;
    };

    this.showWeedingBarChart = function () {
        var _this = this;
        var doneDrawingPromise = new Promise(function (resolve, reject) {
            _this.getWeedingProfile(_this.profileIdEnum.activeProfile).then(function (activeWeedingProfileJSON) {
                _this.drawWeedingBarChart(activeWeedingProfileJSON);
                resolve();
            }).catch(function () {
                reject();
            });
        });
        return doneDrawingPromise;
    };

    this.drawWeedingBarChart = function(weedingProfileJSON) {
        var totalSuggestions = _.sum(_.values(EdelweissAnalytics.numberOfSuggestionsByWeedingType));
        var $allWeedingSuggestionsOption = $("#allWeedingSuggestionsOption");
        if (totalSuggestions === 0) {
            $("#weedingBarChart").html("No weeding suggestions were generated by this combination of settings.");
            $allWeedingSuggestionsOption.empty();
            return;
        }

        $allWeedingSuggestionsOption.html(totalSuggestions + " items total");
        var _this = this;
        $allWeedingSuggestionsOption.off().on("click", function () {
            EdelweissAnalytics.selectedWeedingType = EdelweissAnalytics.weedingType.all;
            _this.doWeedingAnalysisLaneUpdate();
        });

        var weedingTypeColumnName = "weedingType";
        var arrayData = [
            ["Weeding Type", "Number of Suggestions", { role: "style" }, weedingTypeColumnName]
        ];
        if (!weedingProfileJSON.isMaxMonthsUncirculatedIgnored) {
            arrayData.push(["Poorly Circulating",
                EdelweissAnalytics.numberOfSuggestionsByWeedingType[EdelweissAnalytics.weedingType.poorCirculation],
                "color:#FB3928", EdelweissAnalytics.weedingType.poorCirculation]);
        }
        if (!weedingProfileJSON.isMaxCirculationsIgnored) {
            arrayData.push(["Potentially Worn",
                EdelweissAnalytics.numberOfSuggestionsByWeedingType[EdelweissAnalytics.weedingType.worn],
                "color:#00CD23", EdelweissAnalytics.weedingType.worn]);
        }
        if (!weedingProfileJSON.isMaxYearsInSystemIgnored || !weedingProfileJSON.isMaxYearsPubDateIgnored) {
            arrayData.push(["Outdated",
                EdelweissAnalytics.numberOfSuggestionsByWeedingType[EdelweissAnalytics.weedingType.outdated],
                "color:#A3A3A3", EdelweissAnalytics.weedingType.outdated]);
        }
        var data = google.visualization.arrayToDataTable(arrayData);

        var view = new google.visualization.DataView(data);
        var numbersInBarsColumnDef = {
            type: "string",
            role: "annotation",
            calc: function (dt, row) {
                var value = dt.getValue(row, 1);
                if (value == 0) {
                    return "";
                }
                else {
                    if (value >= EdelweissAnalytics.Weeding.maxShown) {
                        return value + "+";
                    } else {
                        return value.toString();
                    }
                }
            }
        };
        view.setColumns([0, 1, 2, numbersInBarsColumnDef]);

        var target = document.getElementById("weedingBarChart");
        var options = {
            bars: "horizontal",
            bar: {
                groupWidth: 40
            },
            width: 410,
            height: 140,
            legend: { position: "none" },
            backgroundColor: "#ffffff",
            vAxis: {
                viewWindow: {
                    min: 0
                },
                gridlines: {
                    color: "none"
                },
                textPosition: "none",
                baselineColor: "#e9ebec"
            },
            hAxis: {
                textStyle: {
                    fontSize: 11
                }
            }
        }; 
        var chart = new google.visualization.ColumnChart(target);

        function onColumnChartClick() {
            var selectedItem = chart.getSelection()[0];
            if (typeof selectedItem !== "undefined") {
                var weedingTypeColumnIndex = _.findIndex(arrayData[0], function (colName) {
                    return colName === weedingTypeColumnName;
                });
                var weedingType = arrayData[selectedItem.row + 1][weedingTypeColumnIndex];
                EdelweissAnalytics.selectedWeedingType = weedingType;
                _this.doWeedingAnalysisLaneUpdate();
            }
        };

        google.visualization.events.addListener(chart, 'select', onColumnChartClick);
        chart.draw(view, options); 

        this.showAnalysisResultsAndHideRunButton();
    };

    this.onEditSelectedProfileIconClick = function () {
        var selectedWeedingProfileId = this.getSelectedWeedingProfileId();
        EdelweissAnalytics.Weeding.openAddOrUpdateProfileModal(selectedWeedingProfileId);
    };

    this.onDeleteSelectedProfileIconClick = function () {
        var _this = this;
        var selectedWeedingProfileId = this.getSelectedWeedingProfileId();
        this.deleteProfile(selectedWeedingProfileId).then(function () {
            _this.saveProfileSelection(EdelweissAnalytics.Weeding.profileIdEnum.newSavedProfile).then(function () { 
                _this.setSelectedWeedingProfile(EdelweissAnalytics.Weeding.profileIdEnum.newSavedProfile);
                $("#editSelectedWeedingProfileIcon").parent("span").webuiPopover("hide");
                _this.removeFromMySavedProfileIdsList(selectedWeedingProfileId);
            }).catch(function () {
                alert(getRes("error_unexpected"));
            });
        }).catch(function () {
            alert(getRes("error_unexpected"));
        });
    };

    this.onSaveEditsOnSelectedProfileIconClick = function () {
        var _this = this;
        _this.getWeedingProfile(EdelweissAnalytics.Weeding.profileIdEnum.activeProfile).then(function (activeWeedingProfileJSON) {
            var newProfileName = $("#editedWeedingProfileName").val();
            var isShared = $("#shareWeedingProfileInput").is(":checked");
            activeWeedingProfileJSON.name = newProfileName;
            activeWeedingProfileJSON.isShared = isShared;
            var selectedProfileId = _this.getSelectedWeedingProfileId();
            _this.addOrUpdateProfile(selectedProfileId, activeWeedingProfileJSON).then(function () {
                $("#editSelectedWeedingProfileIcon").parent("span").webuiPopover("hide");
                $("#savedProfileName").html(newProfileName);
            }).catch(function (error) {
                console.error(error);
                alert(getRes("error_unexpected"));
            });
        }).catch(function (error) {
            console.error(error);
            alert(getRes("error_unexpected"));
        });
    };

    this.onSaveSelectedProfileIconClick = function () {
        var _this = this;
        _this.showProfileActionLoadingAnimation();
        _this.getWeedingProfile(EdelweissAnalytics.Weeding.profileIdEnum.activeProfile).then(function(activeWeedingProfileJSON) {
            var selectedWeedingProfileId = _this.getSelectedWeedingProfileId();
            _this.addOrUpdateProfile(selectedWeedingProfileId, activeWeedingProfileJSON).then(function () {
                $(".selectedWeedingParameter").removeClass("editedWeedingParameter");
                _this.setOriginalValuesOfSelectedWeedingParameters(activeWeedingProfileJSON);
                _this.hideProfileActionLoadingAnimation();
                $("#saveSelectedWeedingProfileIcon").hide();
            }).catch(function (error) {
                console.error(error);
                alert(getRes("error_unexpected"));
                _this.hideProfileActionLoadingAnimation();
            });
        }).catch(function(error) {
            console.error(error);
            alert(getRes("error_unexpected"));
            _this.hideProfileActionLoadingAnimation();
        });
    };

    this.onCopySelectedProfileIconClick = function () {
        var url = "/GetTreelineControl.aspx?controlName=/uc/analytics/CopyWeedingProfile.ascx";
        $("#copySelectedWeedingProfileIcon").parent("span").webuiPopover({
            trigger: "click",
            placement: "auto-bottom",
            width: 335,
            type: "async",
            url: url,
            cache: false,
            multi: false,
        });
    };

    this.onEditSelectedProfileIconClick = function () {
        var params = {
            weedingProfileId: this.getSelectedWeedingProfileId()
        };
        var url = "/GetTreelineControl.aspx?controlName=/uc/analytics/EditWeedingProfile.ascx&" + $.param(params);
        $("#editSelectedWeedingProfileIcon").parent("span").webuiPopover("destroy");
        $("#editSelectedWeedingProfileIcon").parent("span").webuiPopover({
            trigger: "click",
            placement: "auto-bottom",
            width: 300,
            type: "async",
            url: url,
            cache: false,
            multi: false,
        });
    };

    this.onSaveCopyOfSelectedProfileIconClick = function () {
        var _this = this;
        _this.getWeedingProfile(EdelweissAnalytics.Weeding.profileIdEnum.activeProfile).then(function (activeWeedingProfileJSON) {
            var newProfileName = $("#weedingProfileCopyName").val();
            var isShared = $("#shareWeedingProfileInput").is(":checked");
            activeWeedingProfileJSON.name = newProfileName;
            activeWeedingProfileJSON.isShared = isShared;
            activeWeedingProfileJSON.lastViewedDate = null;
            _this.addOrUpdateProfile(EdelweissAnalytics.Weeding.profileIdEnum.newSavedProfile, activeWeedingProfileJSON).then(function (newProfileId) {
                $("#copySelectedWeedingProfileIcon").parent("span").webuiPopover("hide");
                _this.setSelectedWeedingProfile(newProfileId);
                _this.addToMySavedProfileIdsList(newProfileId);
            }).catch(function (error) {
                console.error(error);
                alert(getRes("error_unexpected"));
            });
        }).catch(function (error) {
            console.error(error);
            alert(getRes("error_unexpected"));
        });
    };

    this.setOriginalValuesOfSelectedWeedingParameters = function(weedingProfileJSON) {
        $(".selectedWeedingParameter[data-id='" + this.weedingParameterId.maxMonthsUncirculated + "']")
            .attr("data-originalvalue", weedingProfileJSON.maxMonthsUncirculated);
        $(".selectedWeedingParameter[data-id='" + this.weedingParameterId.maxYearsPubDate + "']") 
            .attr("data-originalvalue", weedingProfileJSON.maxYearsPubDate);
        $(".selectedWeedingParameter[data-id='" + this.weedingParameterId.maxYearsInSystem + "']") 
            .attr("data-originalvalue", weedingProfileJSON.maxYearsInSystem);
        $(".selectedWeedingParameter[data-id='" + this.weedingParameterId.maxCirculations + "']") 
            .attr("data-originalvalue", weedingProfileJSON.maxCirculations);
        $(".selectedWeedingParameter[data-id='" + this.weedingParameterId.minCopiesOnHand + "']") 
            .attr("data-originalvalue", weedingProfileJSON.minCopiesOnHand);

        $(".selectedWeedingParameter[data-id='" + this.weedingParameterId.maxMonthsUncirculated + "']")
            .attr("data-originalisignored", weedingProfileJSON.isMaxMonthsUncirculatedIgnored.toString());
        $(".selectedWeedingParameter[data-id='" + this.weedingParameterId.maxYearsPubDate + "']") 
            .attr("data-originalisignored", weedingProfileJSON.isMaxYearsPubDateIgnored.toString());
        $(".selectedWeedingParameter[data-id='" + this.weedingParameterId.maxYearsInSystem + "']") 
            .attr("data-originalisignored", weedingProfileJSON.isMaxYearsInSystemIgnored.toString());
        $(".selectedWeedingParameter[data-id='" + this.weedingParameterId.maxCirculations + "']") 
            .attr("data-originalisignored", weedingProfileJSON.isMaxCirculationsIgnored.toString());
        $(".selectedWeedingParameter[data-id='" + this.weedingParameterId.minCopiesOnHand + "']") 
            .attr("data-originalisignored", weedingProfileJSON.isMinCopiesOnHandIgnored.toString());
    };

    this.getIsIgnoredAttribute = function (weedingParameterId) {
        return "is" + weedingParameterId.charAt(0).toUpperCase() + weedingParameterId.slice(1) + "Ignored";
    };

    this.isValidParameterValue = function(parameterId, parameterValue) {
        var genericSuccessResponse = {
            isValid: true,
            message: getRes("success")
        };
        var genericFailureResponse = {
            isValid: false,
            message: getRes("invalid_value")
        };
        switch (parameterId) {
            case this.weedingParameterId.maxMonthsUncirculated:
            case this.weedingParameterId.maxCirculations:
            case this.weedingParameterId.maxYearsInSystem:
            case this.weedingParameterId.maxYearsPubDate:
                var possibleInteger = parseFloat(parameterValue);
                if (!Number.isInteger(possibleInteger) || possibleInteger < 0) {
                    return {
                        isValid: false,
                        message: getRes("value_must_be_greater_than_0")
                    }
                } else {
                    return genericSuccessResponse;
                }
                break;
            case this.weedingParameterId.minCopiesOnHand:
                var possibleInteger = parseFloat(parameterValue);
                if (!Number.isInteger(possibleInteger) || possibleInteger < 1) {
                    return {
                        isValid: false,
                        message: getRes("value_must_be_greater_than_1")
                    }
                } else {
                    return genericSuccessResponse;
                }
                break;
            default:
                return genericFailureResponse;
                break;
        }
    };

    this.isAnyWeedingParameterDifferentFromOriginal = function () {
        for (var key in this.weedingParameterId) {
            var parameterId = this.weedingParameterId[key]; 
            var $selectedWeedingParameter = $(".selectedWeedingParameter[data-id='" + parameterId + "']");
            if ($selectedWeedingParameter.hasClass("editedWeedingParameter")) {
                return true;
            }
        }
        return false;
    }

    this.onSaveWeedingParameterIconClick = function (weedingParameterId) {
        var _this = this;
        
        var weedingParameterValue = $("#weedingParameterValue").val();
        var checkValidityResponse = this.isValidParameterValue(weedingParameterId, weedingParameterValue);
        if (!checkValidityResponse.isValid) {
            alert(checkValidityResponse.message);
            return;
        }

        var $loadingAnimation = $("#savingWeedingParameterLoadingAnimation");
        $loadingAnimation.show(); 

        _this.getWeedingProfile(EdelweissAnalytics.Weeding.profileIdEnum.activeProfile).then(function (weedingProfileJSON) {
            var isIgnored = $("#isWeedingParameterIgnored").is(":checked");

            var isWeedingParameterDifferentFromCurrent =
                (weedingProfileJSON[weedingParameterId] != weedingParameterValue)
                || (weedingProfileJSON[_this.getIsIgnoredAttribute(weedingParameterId)] != isIgnored);

            weedingProfileJSON[weedingParameterId] = weedingParameterValue;
            weedingProfileJSON[_this.getIsIgnoredAttribute(weedingParameterId)] = isIgnored;

            _this.addOrUpdateProfile(EdelweissAnalytics.Weeding.profileIdEnum.activeProfile, weedingProfileJSON).then(function () {
                var $selectedWeedingParameter = $(".selectedWeedingParameter[data-id='" + weedingParameterId + "']");
                $selectedWeedingParameter.parent("span").webuiPopover("hide");

                if (isIgnored) {
                    var weedingParameterDisplayValue = getRes("not_set");
                } else {
                    var weedingParameterDisplayValue = weedingParameterValue;
                }
                $selectedWeedingParameter.html(weedingParameterDisplayValue);

                if (isWeedingParameterDifferentFromCurrent) {
                    _this.emptyAnalysisResultsAndShowRunButton();
                }

                var originalValue = $selectedWeedingParameter.attr("data-originalvalue");
                var originalIsIgnored = $selectedWeedingParameter.attr("data-originalisignored");
                var isWeedingParameterDifferentThanOriginal =
                    (weedingParameterValue != originalValue)
                    || (weedingProfileJSON[_this.getIsIgnoredAttribute(weedingParameterId)].toString() != originalIsIgnored);

                if (isWeedingParameterDifferentThanOriginal) {
                    $selectedWeedingParameter.addClass("editedWeedingParameter");
                } else {
                    $selectedWeedingParameter.removeClass("editedWeedingParameter");
                }

                var selectedProfileId = _this.getSelectedWeedingProfileId();
                if (_this.isMySavedProfile(selectedProfileId)) {
                    if (_this.isAnyWeedingParameterDifferentFromOriginal()) {
                        $("#saveSelectedWeedingProfileIcon").show();
                    } else {
                        $("#saveSelectedWeedingProfileIcon").hide();
                    }
                }

                $loadingAnimation.hide();
            }).catch(function (error) {
                console.error(error);
                alert(getRes("error_unexpected"));
                $loadingAnimation.hide();
            });
        }).catch(function(error) {
            console.error(error);
            alert(getRes("error_unexpected"));
            $loadingAnimation.hide();
        });
    };

    this.showProfileActionLoadingAnimation = function () {
        $("#weedingProfilesLoadingAnimation").show();
    };

    this.hideProfileActionLoadingAnimation = function () {
        $("#weedingProfilesLoadingAnimation").hide();
    };

    this.onWeedingProfileSelectionChange = function (selectedWeedingProfileId) {
        var _this = this;
        _this.showProfileActionLoadingAnimation();
        _this.saveProfileSelection(selectedWeedingProfileId).then(function () {
            _this.getWeedingProfile(selectedWeedingProfileId).then(function (weedingProfileJSON) {
                _this.addOrUpdateProfile(EdelweissAnalytics.Weeding.profileIdEnum.activeProfile, weedingProfileJSON).then(function () {
                    _this.showSelectedWeedingSettings(selectedWeedingProfileId).then(function () {
                        $("#savedProfileName").html(weedingProfileJSON.name);
                        $("#saveSelectedWeedingProfileIcon").hide();
                        if (_this.isMySavedProfile(selectedWeedingProfileId)) {
                            $("#editSelectedWeedingProfileIcon").show();
                        } else {
                            $("#editSelectedWeedingProfileIcon").hide();
                        }
                        _this.emptyAnalysisResultsAndShowRunButton();
                        _this.hideProfileActionLoadingAnimation();
                    }).catch(function (error) {
                        console.log(error);
                        alert(getRes("error_unexpected"));
                    });             
                }).catch(function (error) {
                    console.log(error);
                    alert(getRes("error_unexpected"));
                });
            }).catch(function(error) {
                console.log(error);
                alert(getRes("error_unexpected"));
            });
        }).catch(function (error) {
            console.log(error);
            alert(getRes("error_unexpected"));
        });
    };

    this.onRunSelectedProfileIconClick = function () {
        var _this = this;
        logPageHit(getEnumValue("siteContext", "EDELWEISSANALYTICS"),
                getEnumValue("edelweissAnalyticsSiteArea", "WEEDINGANALYSIS"), EdelweissAnalytics.sessionId);
        _this.showProfileActionLoadingAnimation();
        EdelweissAnalytics.selectedWeedingType = EdelweissAnalytics.weedingType.all;
        var selectedWeedingProfileId = _this.getSelectedWeedingProfileId();
        if (selectedWeedingProfileId === _this.profileIdEnum.newSavedProfile) {
            _this.doWeedingAnalysisLaneUpdate();
            _this.hideProfileActionLoadingAnimation();
        } else {
            _this.getWeedingProfile(selectedWeedingProfileId).then(function (weedingProfileJSON) {
                weedingProfileJSON.lastViewedDate = new Date().toISOString();
                _this.addOrUpdateProfile(selectedWeedingProfileId, weedingProfileJSON).then(function () {
                    _this.hideProfileActionLoadingAnimation();
                    _this.doWeedingAnalysisLaneUpdate();
                }).catch(function (error) {
                    console.log(error);
                    alert(getRes("error_unexpected"));
                    _this.hideProfileActionLoadingAnimation();
                });
            }).catch(function (error) {
                console.log(error);
                alert(getRes("error_unexpected"));
                _this.hideProfileActionLoadingAnimation();
            });
        }
    };

    this.doWeedingAnalysisLaneUpdate = function () {
        if (EdelweissAnalytics.isViewingDetailsGrid) {
            if (EdelweissAnalytics.isViewingIndividualLane && !$("#analytics_content").is(":visible")) {
                var dHtml = "<div id='analytics_content' class='analyticsLane_content' style='display: none;'></div>";
                $("#" + EdelweissAnalytics.LaneKeys.WeedingAnalysis + "_AnalyticsWrapper").append(dHtml);
            }
            EdelweissAnalytics.startLaneUpdateProcess(EdelweissAnalytics.LaneKeys.WeedingAnalysis);
        } else {
            toggleAnalyticsDetailByLaneKey(EdelweissAnalytics.LaneKeys.WeedingAnalysis,
                getEnumValue("dashType", "EA_WEEDINGANALYSIS"));
        }
    };

    this.emptyAnalysisResultsAndShowRunButton = function () {
        $("#weedingBarChart").empty();
        $("#allWeedingSuggestionsOption").empty();
        $("#weedingBarChartContainer").hide();
        $("#weedingGridContainer").hide();
        $("#runSelectedWeedingProfileContainer").css("display", "inline-block");
    };

    this.updateSelectedWeedingSettingsWithNewSavedFilter = function (filterId, filterName) {
        if ($("#selectedWeedingProfile-savedFilter").length) {
            if (filterId == 0) {
                $("#selectedWeedingProfile-noSavedFilter").show();
                $("#selectedWeedingProfile-savedFilter").hide();
            } else {
                $("#selectedWeedingProfile-savedFilterName").html(filterName);
                $("#selectedWeedingProfile-noSavedFilter").hide();
                $("#selectedWeedingProfile-savedFilter").show();
            }
        }
    };

    this.showAnalysisResultsAndHideRunButton = function () {
        $("#runSelectedWeedingProfileContainer").hide();
        $("#weedingBarChartContainer").css("display", "inline-block");
        $("#weedingGridContainer").show();
    };

    this.setSelectedWeedingProfile = function (newSelectedProfileId) {
        $("#savedProfileName").attr("data-profileid", newSelectedProfileId); 
        this.onWeedingProfileSelectionChange(newSelectedProfileId);
    };

    this.getSelectedWeedingProfileId = function () {
        return parseInt($("#savedProfileName").attr("data-profileid"));
    };

    var addEmptyColumnsToWeedingResults = function (weedingResults) {
        for (var i = 0; i < weedingResults.length; i++) {
            weedingResults[i].iPage = "";
            weedingResults[i].selectBox = "";
        }
        return weedingResults;
    };

    var addCirculationAndOnHandIfEmptyToWeedingResults = function (weedingResults) {
        for (var i = 0; i < weedingResults.length; i++) {
            if (!weedingResults[i].hasOwnProperty("circulationLifetime")) {
                weedingResults[i].circulationLifetime = 0;
            }
            if (!weedingResults[i].hasOwnProperty("circulationYTD")) {
                weedingResults[i].circulationYTD = 0;
            }
            if (!weedingResults[i].hasOwnProperty("onHand")) {
                weedingResults[i].onHand = 0;
            }
        }
        return weedingResults;
    };

    var addOnlyCopyStatusToWeedingResults = function(weedingResults) {
        for (var i = 0; i < weedingResults.length; i++) {
            weedingResults[i].onlyCopyStatus = getOnlyCopyStatus(weedingResults[i].onHand);
        }
        return weedingResults;
    }

    var makeSeriesNameClickableToOpenTitleModal = function(weedingResults) {
        for (var i = 0; i < weedingResults.length; i++) {
            if (!_.isEmpty(weedingResults[i].seriesName)) {
                weedingResults[i].seriesName = "<span class='clickable' onclick=\"loadModalTitle('" + weedingResults[i].isbn
                    + "', 'dash', '" + getEnumValue("productCompletionSummaryContentType", "SERIESDATA")
                    + "')\">" + weedingResults[i].seriesName + "</span>";
            }
        }
        return weedingResults;
    }

    var getOnlyCopyStatus = function(onHand) {
        var cssClasses = [];
        if (onHand == 1) {
            cssClasses.push("color-red")
            var onlyCopyStatusText = "Y";
        } else {
            var onlyCopyStatusText = "N (" + onHand + ")";
        }
        var onlyCopyStatusHtml = "<span class='" + cssClasses.join(" ") + "'>" + onlyCopyStatusText + "</span>"
        return onlyCopyStatusHtml;
    };

    this.addSelectValuesToWeedingResults = function (weedingResults) {
        for (var i = 0; i < weedingResults.length; i++) {
            weedingResults[i].selectValue = getWeedingSelectValue(weedingResults[i]);
        }
        return weedingResults;
    };

    var getWeedingSelectValue = function(data) {
        if (data.sku && data.itemId) {
            return data.sku + ',' + data.itemId;
        }
        else if(data.sku){
            return data.sku;
        }
        else if (data.itemId) {
            return  ',' + data.itemId;
        }
    }

    var addSelectAllAndUnselectAllButtons = function(thElement) {
        var skuItemText = $(thElement).text();
        var selectAllCheckmark = "<div class='columnSpaced icon-mark-icon iconSVG_NoHover itemAllCheck'"
            + " style= 'margin-left: 0' onclick='EdelweissAnalytics.selectAllItemsOnCurrentPage(\"False\", true);'/>"; 
        var unselectAllCheckmark = "<div class='columnSpaced icon-close-icon iconSVG_NoHover itemAllUnCheck'"
            + " style='margin-left: 5px' onclick='EdelweissAnalytics.deselectAllTitlesOnAllPages();'/>";
        $(thElement).html(selectAllCheckmark + unselectAllCheckmark);
        $(thElement).css("outline", 0);
        $(thElement).unbind("click"); // prevent datatable from sorting on click
    }

    var addCheckBoxes = function() {
        var table = $("#weedingGrid").DataTable();
        if ($("#weedingGrid").DataTable().data().length > 0) {
            $('#weedingGrid > tbody > tr').each(function () {
                var selectVal = table.row($(this)).data().selectValue;
                var index = arrayObjectIndexOf(EdelweissAnalytics.rows, selectVal, "item");
                var isSelected = (index !== -1 && EdelweissAnalytics.rows[index].selected === 1);
                var selectHtml = "<div id='check_" + selectVal + "' class='icon-mark-icon itemCheck"
                if (isSelected) selectHtml += " checkmark_checked";
                selectHtml += "' style='font-size: 14px; cursor: pointer;'"
                selectHtml += " onclick='javascript:EdelweissAnalytics.toggleCheck(\"" + selectVal + "\");' />";
                $($(this).find('td')[0]).html(selectHtml);
            });
        }
        EdelweissAnalytics.setStateOfSelectAllTitlesOnCurrentPageButton();
    }

    var addIpage = function() {
        var table = $("#weedingGrid").DataTable();
        var ipageColumnIndex = table.column("iPage:name").index();
        $('#weedingGrid > tbody > tr').each(function () {
            var isbn = $($(this).find('td')[1]).text();
            if (isbn && isbn != '') {
                if ($('#showIpage').val().toLowerCase() == "true") {
                    $($(this).find('td')[ipageColumnIndex]).html('<img src="/images/ingram/ipage.png" onclick="javascript:GoToIPageDetail(\'' + isbn + '\');"> ');
                }
                else {
                    $($(this).find('td')[ipageColumnIndex]).html('');
                }
            }
        });
    }

    var uncheckSelectAll = function() {
        $('#selectAll').attr('checked', false);
    }

    this.updateWeedingGrid = function(grid, data, lane, callback) {
        if (data.count > EdelweissAnalytics.Weeding.maxShown) {
            $("#weedingCountMessage").html("Showing " + data.records.length.toLocaleString()
                + " of " + data.count.toLocaleString() + " possible results.");
            $("#weedingCountMessage").show();
            $("#weedingCountHelpMessage").show();
        } else {
            $("#weedingCountMessage").html("");
            $("#weedingCountMessage").hide();
            $("#weedingCountHelpMessage").hide();
        }

        data.records = _this.addSelectValuesToWeedingResults(data.records);
        data.records = addEmptyColumnsToWeedingResults(data.records);
        data.records = addCirculationAndOnHandIfEmptyToWeedingResults(data.records);
        data.records = addOnlyCopyStatusToWeedingResults(data.records);
        data.records = makeSeriesNameClickableToOpenTitleModal(data.records);

        grid.DataTable({
            data: data.records,
            columns: [
                { data: "selectBox", width:"10em" },
                { data: "isbn", width:"13em" },
                { data: "itemId", width:"13em" },
                { data: "itemCreatedDate", width:"13em" },
                { data: "title", width:"20em"},
                { data: "author", width:"20em"},
                { data: "seriesName", width:"20em"},
                { data: "pubDate", width: "10em" },
                { data: "branch", width:"10em"},
                { data: "location", width: "13em", defaultContent: "" },
                { data: "collectionCodes", width: "13em" },
                { data: "lastCheckout", width: "13em" },
                { data: "lastReturn", width: "13em" },
                { data: "circulationYTD", width: "13em" },
                { data: "circulationLifetime", width: "13em" },
                { data: "onlyCopyStatus", width: "13em" },
                { data: "barcode", width: "13em" },
                { data: "rationale", width: "20em" },
                { data: "iPage", name: "iPage", width: "13em" },
                { data: "selectValue", name: "selectValue", visible:false } 
            ],
            fnDrawCallback: function () {
                addCheckBoxes();
                addIpage();
                uncheckSelectAll();
                titleGridPageLengthChange("#weedingGrid");
            },
            dom: '<"list_header">fl<rt>ip',
            paging: true,
            bFilter: true,
            info: true,
            bAutoWidth: false,
            processing: true,
            destroy: true,
            pageLength: EdelweissAnalytics.titleGridPageLength
        });

        $("#weedingGrid_wrapper .list_header")
            .load("/GetTreelineControl.aspx?controlName=/uc/listviews/menus/ListView_TopMenu.ascx&ResultType=" + lane.resultType);

        addSelectAllAndUnselectAllButtons($('#weedingGrid > thead > tr > th')[0]);

        return callback(null, "Weeding Grid Initialization Complete");
    };
})();;
// For now, this class assumes our client-side jquery datatables are formed by a list of JSON records
// The list of json records are cached at the global level, and accessible in this class
EdelweissAnalytics.Export = new (function () {
    var _this = this;

    /// PUBLIC METHODS AND DATA ///

    // Supported Export Types
    this.exportType = {
        Weeding: "Weeding",
        CrossStore: "CrossStore",
        CategoryPerformanceComparison: "CategoryPerformanceComparison"
    };

    this.documentTemplateID = {
        Html: 10
    };

    this.userType = {
        Retailer: 0,
        Librarian: 1
    };
    
    this.doJqueryDataTableCsvExport = function (exportType, userType, params) {
        if (exportType === this.exportType.Weeding) {
            if (EdelweissAnalytics.rows.length === 0) {
                modalAlert(getRes("must_select_one_or_more"));
                return null;
            }
        }

        var records = getExportRecords(exportType);
        var sheetHeaders = getSheetHeaders(params);
        var columnHeaders = getColumnHeaders(exportType, userType);
        var fields = getRecordFieldNames(exportType, userType);
        var exportFileName = getExportFileName(exportType);
        if (records === null || columnHeaders === null || fields === null || exportFileName === null) {
            console.warn("One or more export parameters were not specified.");
            modalAlert(getRes("error_unexpected"));
            return;
        }

        ePlus.modules.export.downloadCSVFromJson(exportFileName, columnHeaders, fields, records, sheetHeaders);
    };

    this.PDFExportForWeedingTable = function (options) {
        if (EdelweissAnalytics.rows.length === 0) {
            modalAlert(getRes("must_select_one_or_more"));
            return null;
        }
        var weedingProfileId = EdelweissAnalytics.Weeding.getSelectedWeedingProfileId();
        EdelweissAnalytics.Weeding.getWeedingProfile(weedingProfileId).then(function (weedingProfileJSON) {
            var weedingProfileName = weedingProfileJSON.name;
            var selectedRecords = getExportRecords(_this.exportType.Weeding);
            generatePrintViewTable(selectedRecords);
            var currentDate = new Date();
            var datetime = [(currentDate.getMonth() + 1), currentDate.getDate(), currentDate.getFullYear()].join('_');
            var cssUriArray =
                ['/css/NewLook.css',
                    '/css/jquery/themes/smoothness/jquery-ui.css',
                    '/css/datatables/1.10.12/datatables.newlook.css'
                ];
            
            var pdfDownloadParams = {
                serviceHost: options.serviceHost,
                pdfName: weedingProfileName + " " + datetime + ".pdf",
                cssUriArray: cssUriArray,
                html: $('#weedingGridPrintViewContainer').html(),
                printId: Math.floor(Math.random() * 100001),
                documentTemplateID: _this.documentTemplateID.Html,
                printFormat: "pdf",
                margin: 10,
                landscape: true
            };

            requestPdfDownload(pdfDownloadParams);
        });
    }


    /// PRIVATE METHODS AND DATA ///

    var getExportRecords = function (exportType) {
        var records = null;
        if (exportType === _this.exportType.Weeding) {
            records = getSelectedWeedingAnalysisRecords();
        } else if (exportType === _this.exportType.CrossStore) {
            records = getFilteredCrossStoreDataRows();
        } else if (exportType === _this.exportType.CategoryPerformanceComparison) {
            records = EdelweissAnalytics.categoryPerformanceComparisonRecords;
        }
        return records;
    };

    var getFilteredCrossStoreDataRows = function () {
        var records = [];
        var indexes = $("#crossStoreTable").DataTable().rows({ filter: 'applied' })[0];
        indexes.forEach(function (index) {
            records.push(EdelweissAnalytics.CrossStoreView.exportRecords[index]);
        });
        return records;
    };

    var getSelectedWeedingAnalysisRecords = function () {
        var selectedRecords = [];
        var allRecords = EdelweissAnalytics.weedingAnalysisRecords;
        allRecords = EdelweissAnalytics.Weeding.addSelectValuesToWeedingResults(allRecords);
        EdelweissAnalytics.rows.forEach(function (row) {
            var selectedRecord = _.find(allRecords, function (record) {
                return record.selectValue === row.item;
            });
            if (typeof selectedRecord !== "undefined" && selectedRecord !== null) {
                selectedRecords.push(selectedRecord);
            }
        });
        return selectedRecords;
    };

    var getSheetHeaders = function (params) {
        var sheetHeaders = "";

        var title = getRes('title');
        var sales = getRes('sales');
        var market = getRes('market');
        var sales = getRes('sales');
        var isbn = 'ISBN';

        if (params) {
            if (params.sku) {
                sheetHeaders += isbn + ': ' + params.sku + '\n';
            }

            if (params.title) {
                sheetHeaders += title + ': ' + params.title + '\n';
            }

            if (params.market) {
                sheetHeaders += market + ': ' + params.market + '\n';
            }

            if (params.timeframe) {
                sheetHeaders += sales + ': ' + params.timeframe + '\n';
            }
        }

        return sheetHeaders;
    }

    var getColumnHeaders = function (exportType, userType) {
        var columnHeaders = "";

        var createdDate = getRes('created_date');
        var title = getRes('title');
        var author = getRes('author');
        var pubDate = getRes('pub_date');
        var branch = getRes('branch');
        var callNumber = getRes('call_number');
        var collectionCodes = getRes('collection_codes');
        var lastCheckedOut = getRes('last_checked_out');
        var ytdCirc = getRes('ytd_circ');
        var lifetimeCirc = getRes('lifetime_circ');
        var barcode = getRes('barcode');
        var rationale = getRes('rationale');
        var storeRegion = getRes('store_region');
        var name = getRes('name');
        var oh = getRes('oh_abbreviation');
        var oo = getRes('oo_abbreviation');
        var historicalSales = getRes('historical_sales');
        var category = getRes('category');
        var sales = getRes('sales');
        var percentTotalSales = getRes('percent_total_sales');
        var unitSales = getRes('unit_sales');
        var percentTotalUnitSales = getRes('percent_total_unit_sales');
        var averageInventory = getRes('average_inventory');
        var percentTotalInvetory = getRes('percent_total_inventory');
        var currentItems = getRes('current_items');
        var percentTotalItems = getRes('percent_total_items');
        var turnover = getRes('turnover');
        var status = getRes('status');
        var circulation = getRes('circulation');
        var percentTotalCirculation = getRes('percent_total_circulation');
        var percentColleciton = getRes('percent_collection');
        var sales = getRes('sales');
        var isbn = 'ISBN';
        var itemId = 'ItemId';

        if (exportType === _this.exportType.Weeding) {
            columnHeaders += [isbn, itemId, createdDate, title, author, pubDate, branch, callNumber, collectionCodes, lastCheckedOut, ytdCirc, lifetimeCirc, barcode, rationale].join();
        } else if (exportType === _this.exportType.CrossStore) {
            columnHeaders += [storeRegion + ' / ' + name, oh, oo, historicalSales].join();
        } else if (exportType === _this.exportType.CategoryPerformanceComparison) {
            if (userType === _this.userType.Retailer) {
                columnHeaders += [category, sales, percentTotalSales, unitSales, percentTotalUnitSales, averageInventory, percentTotalInvetory, currentItems, percentTotalItems, turnover, status].join()
            } else {
                columnHeaders += [category, circulation, percentTotalCirculation, currentItems, percentColleciton, turnover, status].join();
            }
        }

        return columnHeaders.split(",");
    };

    var getRecordFieldNames = function (exportType, userType) {
        var fields = null;
        if (exportType === _this.exportType.Weeding) {
            fields = ["sku", "itemId", "itemCreatedDate", "title", "author", "pubDate", "branch", "location",
                "collectionCodes", "lastCheckout", "circulationYTD", "circulationLifetime", "barcode", "rationale"];
        } else if (exportType === _this.exportType.CrossStore) {
            var fields = ["storeRegionOrName", "onHand", "onOrder", "historicalSales"];
        } else if (exportType === _this.exportType.CategoryPerformanceComparison) {
            if (userType === _this.userType.Retailer) {
                fields = ["category", "sales", "percentOfTotalSales", "unitSales", "percentOfTotalUnitSales",
                    "averageInventory", "percentOfTotalInventory", "numberOfItemsInCollection", "percentOfTotalCollection",
                    "turnover", "status"]; 
            } else {
                fields = ["category", "unitSales", "percentOfTotalUnitSales", "numberOfItemsInCollection", "percentOfTotalCollection",
                    "turnover", "status"];
            }
        }
        return fields;
    };

    var getExportFileName = function (exportType) {
        var exportFileName = null;
        if (exportType === _this.exportType.Weeding) {
            exportFileName = "WeedingAnalysisData.csv";
        } else if (exportType === _this.exportType.CrossStore) {
            exportFileName = "CrossStoreData.csv";
        } else if (exportType === _this.exportType.CategoryPerformanceComparison) {
            exportFileName = "CategoryPerformanceComparisonData.csv";
        }
        return exportFileName;
    };

    var generatePrintViewTable = function (records) {
        $("#weedingGridPrintView").DataTable({
            data: records,
            columns: [
                { data: "isbn", width: "13em" },
                { data: "itemId", width: "13em" },
                { data: "itemCreatedDate", width: "13em" },
                { data: "title", width: "20em" },
                { data: "author", width: "20em" },
                { data: "pubDate", width: "10em" },
                { data: "branch", width: "10em" },
                { data: "location", width: "13em", defaultContent: "" },
                { data: "collectionCodes", width: "13em" },
                { data: "lastCheckout", width: "13em" },
                { data: "circulationYTD", width: "13em" },
                { data: "circulationLifetime", width: "13em" },
                { data: EdelweissAnalytics.renderBarcodeImg, width: "15em" },
                { data: "rationale", width: "20em" }
            ],
            order: [],
            paging: false,
            bFilter: false,
            bAutoWidth: false,
            info: false,
            destroy: true
        });
    };

    var requestPdfDownload = function (pdfDownloadParams) {
        pdfDownloadParams.pdfName = pdfDownloadParams.pdfName ? encodeURIComponent(pdfDownloadParams.pdfName) : '';
        pdfDownloadParams.cssUriArray = pdfDownloadParams.cssUriArray ? encodeURIComponent(pdfDownloadParams.cssUriArray.join()) : '';
        pdfDownloadParams.html = pdfDownloadParams.html ? encodeURIComponent(pdfDownloadParams.html) : '';
        inputs = createPdfFormInputs(pdfDownloadParams);
        var url = pdfDownloadParams.serviceHost + "/printing/GenerateTitleListPDF.aspx";
        $('<form action=' + url + ' method="post">' + inputs + '</form>')
            .appendTo('body').submit().remove();      
        return false;
    }

    var createPdfFormInputs = function (pdfDownloadParams) {
        var inputs = '';
        for(var param in pdfDownloadParams) {
            if (pdfDownloadParams.hasOwnProperty(param)) {
                inputs += '<input type="hidden" name="' + param + '" value="' + pdfDownloadParams[param] + '" />';
            }
        }

        return inputs;
    }
})();;
EdelweissAnalytics.TitleOwnershipClaims = new (function () {
    this.createClaim = function (config) {
        createClaimInDatabase(config.sku, config.familyId, config.applicationIdentifier,
            config.suggestedEquivalentSku).then(function () {
                config.onSuccess();
            }).catch(function () {
                config.onFailure();
            });
    }; 

    function createClaimInDatabase(sku, familyId, applicationIdentifier, suggestedEquivalentSku) {
        var createClaimUrl = "api/v2/analysis/titleOwnershipClaim";
        if (typeof suggestedEquivalentSku === "undefined" || suggestedEquivalentSku === null) {
            suggestedEquivalentSku = "";
        }
        var titleOwnershipClaimDto = {
            sku: sku,
            familyId: familyId,
            applicationIdentifier: applicationIdentifier,
            suggestedEquivalentSku: suggestedEquivalentSku
        };
        var createClaimPromise = new Promise(function (resolve, reject) {
            $.ajax({
                type: "POST",
                url: createClaimUrl,
                dataType: "json",
                data: titleOwnershipClaimDto,
                success: function () {
                    resolve();
                },
                error: function () {
                    reject();
                }
            });
        });
        return createClaimPromise;
    };

    this.deleteClaim = function (config) {
        deleteClaimInDatabase(config.sku, config.familyId, config.applicationIdentifier).then(function () {
            config.onSuccess();
        }).catch(function () {
            config.onFailure();
        });
    };

    function deleteClaimInDatabase(sku, familyId, applicationIdentifier) {
        var deleteClaimUrl = "api/v1/analysis/titleOwnershipClaim/delete";
        var titleOwnershipClaimDto = {
            sku: sku,
            familyId: familyId,
            applicationIdentifier: applicationIdentifier
        };
        var deleteClaimPromise = new Promise(function (resolve, reject) {
            $.ajax({
                type: "POST",
                url: deleteClaimUrl,
                dataType: "json",
                data: titleOwnershipClaimDto,
                success: function () {
                    resolve();
                },
                error: function () {
                    reject();
                }
            });
        });
        return deleteClaimPromise;
    };

    this.openTitleOwnershipClaimConfirmation = function () {
        var url = "/GetTreelineControl.aspx?controlName=/uc/analytics/TitleOwnershipClaimConfirmation.ascx";
        var onLoad = function (popModalContentElem) {
            setTimeout(function () {
                $(popModalContentElem).fadeOut(800, function () {
                    closeModal();
                });
            }, 2000);
        };
        openModal(url, "500px", "100px", onLoad, null, null, null, "hidden");
    };
});;
EdelweissAnalytics.Series = new (function () {
    this.renderSeriesDataTitlesGrid = function (skus, orgId, storeId, monthsBack, $grid, $loadingAnimation) {
        var params = {
            orgId: orgId,
            storeId: storeId,
            monthsBack: monthsBack
        };
        $.ajax({
            url: "api/v1/analysis/series/holdingsTable?" + $.param(params),
            type: "POST",
            data: JSON.stringify(skus),
            contentType: 'application/json; charset=utf-8'
        }).done(function (data) {
            applyInitialSeriesDataTitlesGridSettings();
            drawSeriesDataTitlesGrid($grid, data.jqueryDataTable, storeId, monthsBack);
            $loadingAnimation.hide();
        }).fail(function () {
            $loadingAnimation.hide();
            alert(getRes("error_unexpected"));
        });
    };

    var drawSeriesDataTitlesGrid = function($grid, jqueryDataTable, storeId, monthsBack) {
        $grid.dataTable({
            "destroy": true,
            "bPaginate": false,
            "bFilter": false,
            "bInfo": false,
            "language": {
                "processing": "<i>" + getRes("loading") + "...</i>"
            },
            "bAutoWidth": false,
            "columns": jqueryDataTable.aoColumns,
            "data": jqueryDataTable.aaData,
            "deferLoading": jqueryDataTable.iTotalDisplayRecords,
            "fnDrawCallback": function (oSettings) {
                initializeSeriesDataTitlesGrid(oSettings, $grid, storeId, monthsBack);
            },
            "paginationType": jqueryDataTable.sPaginationType,
            "iDisplayLength": jqueryDataTable.iDisplayLength,
            "columnDefs": [
                formatDates(jqueryDataTable.aoColumns),
                createCompositeColumn(jqueryDataTable.aoColumns,
                    'col-title', 'col-author', ['col-title--author'],
                    ['titleFlex_Name', 'accFont']),
                updateSingleColumn(jqueryDataTable.aoColumns,
                    'col-version--isbn', ['clickableVersion'])
            ]
        });
    }

    var initializeSeriesDataTitlesGrid = function(oSettings, $grid, storeId, monthsBack) {
        EdelweissAnalytics.stylePercentageData($grid);
        EdelweissAnalytics.addClickableVersionListener($grid, [storeId], monthsBack, true);
    }

    var applyInitialSeriesDataTitlesGridSettings = function() {
        $.fn.dataTableExt.oStdClasses.sStripeEven = "wFil tlList even altRow";
        $.fn.dataTableExt.oStdClasses.sStripeOdd = "wFil tlList odd stdRow";
    }      

    this.addTooltipHoverToShelfDayFlamesInFamilyDetailsView = function(familyDetailsSku) {
        var skus = getSkusForAllShelfDayFlames(familyDetailsSku);
        skus.forEach(function (sku) {
            var flameTooltipControlParams = {
                sku: sku
            };
            var popoverOptions = {
                type: "async",
                trigger: "hover",
                url: "/GetTreelineControl.aspx?controlName=/uc/analytics/FlameTooltip.ascx&" +
                    $.param(flameTooltipControlParams),
                cache: false,
                multi: true,
                placement: "auto-bottom"
            };
            var $shelfDayFlame = getShelfDayFlameJquerySelector(familyDetailsSku, sku);
            $shelfDayFlame.webuiPopover(popoverOptions);
        });
    };

    var getSkusForAllShelfDayFlames = function (familyDetailsSku) {
        var skuElements = $("#childDetail_" + familyDetailsSku).find(".rowSku");
        var skus = [];
        for (var i = 0; i < skuElements.length; i++) {
            var sku = $(skuElements[i]).text();
            skus.push(sku);
        }
        return skus;
    };

    var getShelfDayFlameJquerySelector = function (familyDetailsSku, rowSku) {
        var $shelfDayFlame = $("#childDetail_" + familyDetailsSku).find(".rowSku:contains('" + rowSku + "')").
            closest("tr").find(".col-activity span[class^='icon-flame']");
        return $shelfDayFlame;
    };
});;
/*!
 * @overview es6-promise - a tiny implementation of Promises/A+.
 * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald)
 * @license   Licensed under MIT license
 *            See https://raw.githubusercontent.com/stefanpenner/es6-promise/master/LICENSE
 * @version   4.1.0+f9a5575b
 */

(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
    (global.ES6Promise = factory());
}(this, (function () { 'use strict';

function objectOrFunction(x) {
  return typeof x === 'function' || typeof x === 'object' && x !== null;
}

function isFunction(x) {
  return typeof x === 'function';
}

var _isArray = undefined;
if (!Array.isArray) {
  _isArray = function (x) {
    return Object.prototype.toString.call(x) === '[object Array]';
  };
} else {
  _isArray = Array.isArray;
}

var isArray = _isArray;

var len = 0;
var vertxNext = undefined;
var customSchedulerFn = undefined;

var asap = function asap(callback, arg) {
  queue[len] = callback;
  queue[len + 1] = arg;
  len += 2;
  if (len === 2) {
    // If len is 2, that means that we need to schedule an async flush.
    // If additional callbacks are queued before the queue is flushed, they
    // will be processed by this flush that we are scheduling.
    if (customSchedulerFn) {
      customSchedulerFn(flush);
    } else {
      scheduleFlush();
    }
  }
};

function setScheduler(scheduleFn) {
  customSchedulerFn = scheduleFn;
}

function setAsap(asapFn) {
  asap = asapFn;
}

var browserWindow = typeof window !== 'undefined' ? window : undefined;
var browserGlobal = browserWindow || {};
var BrowserMutationObserver = browserGlobal.MutationObserver || browserGlobal.WebKitMutationObserver;
var isNode = typeof self === 'undefined' && typeof process !== 'undefined' && ({}).toString.call(process) === '[object process]';

// test for web worker but not in IE10
var isWorker = typeof Uint8ClampedArray !== 'undefined' && typeof importScripts !== 'undefined' && typeof MessageChannel !== 'undefined';

// node
function useNextTick() {
  // node version 0.10.x displays a deprecation warning when nextTick is used recursively
  // see https://github.com/cujojs/when/issues/410 for details
  return function () {
    return process.nextTick(flush);
  };
}

// vertx
function useVertxTimer() {
  if (typeof vertxNext !== 'undefined') {
    return function () {
      vertxNext(flush);
    };
  }

  return useSetTimeout();
}

function useMutationObserver() {
  var iterations = 0;
  var observer = new BrowserMutationObserver(flush);
  var node = document.createTextNode('');
  observer.observe(node, { characterData: true });

  return function () {
    node.data = iterations = ++iterations % 2;
  };
}

// web worker
function useMessageChannel() {
  var channel = new MessageChannel();
  channel.port1.onmessage = flush;
  return function () {
    return channel.port2.postMessage(0);
  };
}

function useSetTimeout() {
  // Store setTimeout reference so es6-promise will be unaffected by
  // other code modifying setTimeout (like sinon.useFakeTimers())
  var globalSetTimeout = setTimeout;
  return function () {
    return globalSetTimeout(flush, 1);
  };
}

var queue = new Array(1000);
function flush() {
  for (var i = 0; i < len; i += 2) {
    var callback = queue[i];
    var arg = queue[i + 1];

    callback(arg);

    queue[i] = undefined;
    queue[i + 1] = undefined;
  }

  len = 0;
}

function attemptVertx() {
  try {
    var r = require;
    var vertx = r('vertx');
    vertxNext = vertx.runOnLoop || vertx.runOnContext;
    return useVertxTimer();
  } catch (e) {
    return useSetTimeout();
  }
}

var scheduleFlush = undefined;
// Decide what async method to use to triggering processing of queued callbacks:
if (isNode) {
  scheduleFlush = useNextTick();
} else if (BrowserMutationObserver) {
  scheduleFlush = useMutationObserver();
} else if (isWorker) {
  scheduleFlush = useMessageChannel();
} else if (browserWindow === undefined && typeof require === 'function') {
  scheduleFlush = attemptVertx();
} else {
  scheduleFlush = useSetTimeout();
}

function then(onFulfillment, onRejection) {
  var _arguments = arguments;

  var parent = this;

  var child = new this.constructor(noop);

  if (child[PROMISE_ID] === undefined) {
    makePromise(child);
  }

  var _state = parent._state;

  if (_state) {
    (function () {
      var callback = _arguments[_state - 1];
      asap(function () {
        return invokeCallback(_state, child, callback, parent._result);
      });
    })();
  } else {
    subscribe(parent, child, onFulfillment, onRejection);
  }

  return child;
}

/**
  `Promise.resolve` returns a promise that will become resolved with the
  passed `value`. It is shorthand for the following:

  ```javascript
  let promise = new Promise(function(resolve, reject){
    resolve(1);
  });

  promise.then(function(value){
    // value === 1
  });
  ```

  Instead of writing the above, your code now simply becomes the following:

  ```javascript
  let promise = Promise.resolve(1);

  promise.then(function(value){
    // value === 1
  });
  ```

  @method resolve
  @static
  @param {Any} value value that the returned promise will be resolved with
  Useful for tooling.
  @return {Promise} a promise that will become fulfilled with the given
  `value`
*/
function resolve(object) {
  /*jshint validthis:true */
  var Constructor = this;

  if (object && typeof object === 'object' && object.constructor === Constructor) {
    return object;
  }

  var promise = new Constructor(noop);
  _resolve(promise, object);
  return promise;
}

var PROMISE_ID = Math.random().toString(36).substring(16);

function noop() {}

var PENDING = void 0;
var FULFILLED = 1;
var REJECTED = 2;

var GET_THEN_ERROR = new ErrorObject();

function selfFulfillment() {
  return new TypeError("You cannot resolve a promise with itself");
}

function cannotReturnOwn() {
  return new TypeError('A promises callback cannot return that same promise.');
}

function getThen(promise) {
  try {
    return promise.then;
  } catch (error) {
    GET_THEN_ERROR.error = error;
    return GET_THEN_ERROR;
  }
}

function tryThen(then, value, fulfillmentHandler, rejectionHandler) {
  try {
    then.call(value, fulfillmentHandler, rejectionHandler);
  } catch (e) {
    return e;
  }
}

function handleForeignThenable(promise, thenable, then) {
  asap(function (promise) {
    var sealed = false;
    var error = tryThen(then, thenable, function (value) {
      if (sealed) {
        return;
      }
      sealed = true;
      if (thenable !== value) {
        _resolve(promise, value);
      } else {
        fulfill(promise, value);
      }
    }, function (reason) {
      if (sealed) {
        return;
      }
      sealed = true;

      _reject(promise, reason);
    }, 'Settle: ' + (promise._label || ' unknown promise'));

    if (!sealed && error) {
      sealed = true;
      _reject(promise, error);
    }
  }, promise);
}

function handleOwnThenable(promise, thenable) {
  if (thenable._state === FULFILLED) {
    fulfill(promise, thenable._result);
  } else if (thenable._state === REJECTED) {
    _reject(promise, thenable._result);
  } else {
    subscribe(thenable, undefined, function (value) {
      return _resolve(promise, value);
    }, function (reason) {
      return _reject(promise, reason);
    });
  }
}

function handleMaybeThenable(promise, maybeThenable, then$$) {
  if (maybeThenable.constructor === promise.constructor && then$$ === then && maybeThenable.constructor.resolve === resolve) {
    handleOwnThenable(promise, maybeThenable);
  } else {
    if (then$$ === GET_THEN_ERROR) {
      _reject(promise, GET_THEN_ERROR.error);
      GET_THEN_ERROR.error = null;
    } else if (then$$ === undefined) {
      fulfill(promise, maybeThenable);
    } else if (isFunction(then$$)) {
      handleForeignThenable(promise, maybeThenable, then$$);
    } else {
      fulfill(promise, maybeThenable);
    }
  }
}

function _resolve(promise, value) {
  if (promise === value) {
    _reject(promise, selfFulfillment());
  } else if (objectOrFunction(value)) {
    handleMaybeThenable(promise, value, getThen(value));
  } else {
    fulfill(promise, value);
  }
}

function publishRejection(promise) {
  if (promise._onerror) {
    promise._onerror(promise._result);
  }

  publish(promise);
}

function fulfill(promise, value) {
  if (promise._state !== PENDING) {
    return;
  }

  promise._result = value;
  promise._state = FULFILLED;

  if (promise._subscribers.length !== 0) {
    asap(publish, promise);
  }
}

function _reject(promise, reason) {
  if (promise._state !== PENDING) {
    return;
  }
  promise._state = REJECTED;
  promise._result = reason;

  asap(publishRejection, promise);
}

function subscribe(parent, child, onFulfillment, onRejection) {
  var _subscribers = parent._subscribers;
  var length = _subscribers.length;

  parent._onerror = null;

  _subscribers[length] = child;
  _subscribers[length + FULFILLED] = onFulfillment;
  _subscribers[length + REJECTED] = onRejection;

  if (length === 0 && parent._state) {
    asap(publish, parent);
  }
}

function publish(promise) {
  var subscribers = promise._subscribers;
  var settled = promise._state;

  if (subscribers.length === 0) {
    return;
  }

  var child = undefined,
      callback = undefined,
      detail = promise._result;

  for (var i = 0; i < subscribers.length; i += 3) {
    child = subscribers[i];
    callback = subscribers[i + settled];

    if (child) {
      invokeCallback(settled, child, callback, detail);
    } else {
      callback(detail);
    }
  }

  promise._subscribers.length = 0;
}

function ErrorObject() {
  this.error = null;
}

var TRY_CATCH_ERROR = new ErrorObject();

function tryCatch(callback, detail) {
  try {
    return callback(detail);
  } catch (e) {
    TRY_CATCH_ERROR.error = e;
    return TRY_CATCH_ERROR;
  }
}

function invokeCallback(settled, promise, callback, detail) {
  var hasCallback = isFunction(callback),
      value = undefined,
      error = undefined,
      succeeded = undefined,
      failed = undefined;

  if (hasCallback) {
    value = tryCatch(callback, detail);

    if (value === TRY_CATCH_ERROR) {
      failed = true;
      error = value.error;
      value.error = null;
    } else {
      succeeded = true;
    }

    if (promise === value) {
      _reject(promise, cannotReturnOwn());
      return;
    }
  } else {
    value = detail;
    succeeded = true;
  }

  if (promise._state !== PENDING) {
    // noop
  } else if (hasCallback && succeeded) {
      _resolve(promise, value);
    } else if (failed) {
      _reject(promise, error);
    } else if (settled === FULFILLED) {
      fulfill(promise, value);
    } else if (settled === REJECTED) {
      _reject(promise, value);
    }
}

function initializePromise(promise, resolver) {
  try {
    resolver(function resolvePromise(value) {
      _resolve(promise, value);
    }, function rejectPromise(reason) {
      _reject(promise, reason);
    });
  } catch (e) {
    _reject(promise, e);
  }
}

var id = 0;
function nextId() {
  return id++;
}

function makePromise(promise) {
  promise[PROMISE_ID] = id++;
  promise._state = undefined;
  promise._result = undefined;
  promise._subscribers = [];
}

function Enumerator(Constructor, input) {
  this._instanceConstructor = Constructor;
  this.promise = new Constructor(noop);

  if (!this.promise[PROMISE_ID]) {
    makePromise(this.promise);
  }

  if (isArray(input)) {
    this._input = input;
    this.length = input.length;
    this._remaining = input.length;

    this._result = new Array(this.length);

    if (this.length === 0) {
      fulfill(this.promise, this._result);
    } else {
      this.length = this.length || 0;
      this._enumerate();
      if (this._remaining === 0) {
        fulfill(this.promise, this._result);
      }
    }
  } else {
    _reject(this.promise, validationError());
  }
}

function validationError() {
  return new Error('Array Methods must be provided an Array');
};

Enumerator.prototype._enumerate = function () {
  var length = this.length;
  var _input = this._input;

  for (var i = 0; this._state === PENDING && i < length; i++) {
    this._eachEntry(_input[i], i);
  }
};

Enumerator.prototype._eachEntry = function (entry, i) {
  var c = this._instanceConstructor;
  var resolve$$ = c.resolve;

  if (resolve$$ === resolve) {
    var _then = getThen(entry);

    if (_then === then && entry._state !== PENDING) {
      this._settledAt(entry._state, i, entry._result);
    } else if (typeof _then !== 'function') {
      this._remaining--;
      this._result[i] = entry;
    } else if (c === Promise) {
      var promise = new c(noop);
      handleMaybeThenable(promise, entry, _then);
      this._willSettleAt(promise, i);
    } else {
      this._willSettleAt(new c(function (resolve$$) {
        return resolve$$(entry);
      }), i);
    }
  } else {
    this._willSettleAt(resolve$$(entry), i);
  }
};

Enumerator.prototype._settledAt = function (state, i, value) {
  var promise = this.promise;

  if (promise._state === PENDING) {
    this._remaining--;

    if (state === REJECTED) {
      _reject(promise, value);
    } else {
      this._result[i] = value;
    }
  }

  if (this._remaining === 0) {
    fulfill(promise, this._result);
  }
};

Enumerator.prototype._willSettleAt = function (promise, i) {
  var enumerator = this;

  subscribe(promise, undefined, function (value) {
    return enumerator._settledAt(FULFILLED, i, value);
  }, function (reason) {
    return enumerator._settledAt(REJECTED, i, reason);
  });
};

/**
  `Promise.all` accepts an array of promises, and returns a new promise which
  is fulfilled with an array of fulfillment values for the passed promises, or
  rejected with the reason of the first passed promise to be rejected. It casts all
  elements of the passed iterable to promises as it runs this algorithm.

  Example:

  ```javascript
  let promise1 = resolve(1);
  let promise2 = resolve(2);
  let promise3 = resolve(3);
  let promises = [ promise1, promise2, promise3 ];

  Promise.all(promises).then(function(array){
    // The array here would be [ 1, 2, 3 ];
  });
  ```

  If any of the `promises` given to `all` are rejected, the first promise
  that is rejected will be given as an argument to the returned promises's
  rejection handler. For example:

  Example:

  ```javascript
  let promise1 = resolve(1);
  let promise2 = reject(new Error("2"));
  let promise3 = reject(new Error("3"));
  let promises = [ promise1, promise2, promise3 ];

  Promise.all(promises).then(function(array){
    // Code here never runs because there are rejected promises!
  }, function(error) {
    // error.message === "2"
  });
  ```

  @method all
  @static
  @param {Array} entries array of promises
  @param {String} label optional string for labeling the promise.
  Useful for tooling.
  @return {Promise} promise that is fulfilled when all `promises` have been
  fulfilled, or rejected if any of them become rejected.
  @static
*/
function all(entries) {
  return new Enumerator(this, entries).promise;
}

/**
  `Promise.race` returns a new promise which is settled in the same way as the
  first passed promise to settle.

  Example:

  ```javascript
  let promise1 = new Promise(function(resolve, reject){
    setTimeout(function(){
      resolve('promise 1');
    }, 200);
  });

  let promise2 = new Promise(function(resolve, reject){
    setTimeout(function(){
      resolve('promise 2');
    }, 100);
  });

  Promise.race([promise1, promise2]).then(function(result){
    // result === 'promise 2' because it was resolved before promise1
    // was resolved.
  });
  ```

  `Promise.race` is deterministic in that only the state of the first
  settled promise matters. For example, even if other promises given to the
  `promises` array argument are resolved, but the first settled promise has
  become rejected before the other promises became fulfilled, the returned
  promise will become rejected:

  ```javascript
  let promise1 = new Promise(function(resolve, reject){
    setTimeout(function(){
      resolve('promise 1');
    }, 200);
  });

  let promise2 = new Promise(function(resolve, reject){
    setTimeout(function(){
      reject(new Error('promise 2'));
    }, 100);
  });

  Promise.race([promise1, promise2]).then(function(result){
    // Code here never runs
  }, function(reason){
    // reason.message === 'promise 2' because promise 2 became rejected before
    // promise 1 became fulfilled
  });
  ```

  An example real-world use case is implementing timeouts:

  ```javascript
  Promise.race([ajax('foo.json'), timeout(5000)])
  ```

  @method race
  @static
  @param {Array} promises array of promises to observe
  Useful for tooling.
  @return {Promise} a promise which settles in the same way as the first passed
  promise to settle.
*/
function race(entries) {
  /*jshint validthis:true */
  var Constructor = this;

  if (!isArray(entries)) {
    return new Constructor(function (_, reject) {
      return reject(new TypeError('You must pass an array to race.'));
    });
  } else {
    return new Constructor(function (resolve, reject) {
      var length = entries.length;
      for (var i = 0; i < length; i++) {
        Constructor.resolve(entries[i]).then(resolve, reject);
      }
    });
  }
}

/**
  `Promise.reject` returns a promise rejected with the passed `reason`.
  It is shorthand for the following:

  ```javascript
  let promise = new Promise(function(resolve, reject){
    reject(new Error('WHOOPS'));
  });

  promise.then(function(value){
    // Code here doesn't run because the promise is rejected!
  }, function(reason){
    // reason.message === 'WHOOPS'
  });
  ```

  Instead of writing the above, your code now simply becomes the following:

  ```javascript
  let promise = Promise.reject(new Error('WHOOPS'));

  promise.then(function(value){
    // Code here doesn't run because the promise is rejected!
  }, function(reason){
    // reason.message === 'WHOOPS'
  });
  ```

  @method reject
  @static
  @param {Any} reason value that the returned promise will be rejected with.
  Useful for tooling.
  @return {Promise} a promise rejected with the given `reason`.
*/
function reject(reason) {
  /*jshint validthis:true */
  var Constructor = this;
  var promise = new Constructor(noop);
  _reject(promise, reason);
  return promise;
}

function needsResolver() {
  throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
}

function needsNew() {
  throw new TypeError("Failed to construct 'Promise': Please use the 'new' operator, this object constructor cannot be called as a function.");
}

/**
  Promise objects represent the eventual result of an asynchronous operation. The
  primary way of interacting with a promise is through its `then` method, which
  registers callbacks to receive either a promise's eventual value or the reason
  why the promise cannot be fulfilled.

  Terminology
  -----------

  - `promise` is an object or function with a `then` method whose behavior conforms to this specification.
  - `thenable` is an object or function that defines a `then` method.
  - `value` is any legal JavaScript value (including undefined, a thenable, or a promise).
  - `exception` is a value that is thrown using the throw statement.
  - `reason` is a value that indicates why a promise was rejected.
  - `settled` the final resting state of a promise, fulfilled or rejected.

  A promise can be in one of three states: pending, fulfilled, or rejected.

  Promises that are fulfilled have a fulfillment value and are in the fulfilled
  state.  Promises that are rejected have a rejection reason and are in the
  rejected state.  A fulfillment value is never a thenable.

  Promises can also be said to *resolve* a value.  If this value is also a
  promise, then the original promise's settled state will match the value's
  settled state.  So a promise that *resolves* a promise that rejects will
  itself reject, and a promise that *resolves* a promise that fulfills will
  itself fulfill.


  Basic Usage:
  ------------

  ```js
  let promise = new Promise(function(resolve, reject) {
    // on success
    resolve(value);

    // on failure
    reject(reason);
  });

  promise.then(function(value) {
    // on fulfillment
  }, function(reason) {
    // on rejection
  });
  ```

  Advanced Usage:
  ---------------

  Promises shine when abstracting away asynchronous interactions such as
  `XMLHttpRequest`s.

  ```js
  function getJSON(url) {
    return new Promise(function(resolve, reject){
      let xhr = new XMLHttpRequest();

      xhr.open('GET', url);
      xhr.onreadystatechange = handler;
      xhr.responseType = 'json';
      xhr.setRequestHeader('Accept', 'application/json');
      xhr.send();

      function handler() {
        if (this.readyState === this.DONE) {
          if (this.status === 200) {
            resolve(this.response);
          } else {
            reject(new Error('getJSON: `' + url + '` failed with status: [' + this.status + ']'));
          }
        }
      };
    });
  }

  getJSON('/posts.json').then(function(json) {
    // on fulfillment
  }, function(reason) {
    // on rejection
  });
  ```

  Unlike callbacks, promises are great composable primitives.

  ```js
  Promise.all([
    getJSON('/posts'),
    getJSON('/comments')
  ]).then(function(values){
    values[0] // => postsJSON
    values[1] // => commentsJSON

    return values;
  });
  ```

  @class Promise
  @param {function} resolver
  Useful for tooling.
  @constructor
*/
function Promise(resolver) {
  this[PROMISE_ID] = nextId();
  this._result = this._state = undefined;
  this._subscribers = [];

  if (noop !== resolver) {
    typeof resolver !== 'function' && needsResolver();
    this instanceof Promise ? initializePromise(this, resolver) : needsNew();
  }
}

Promise.all = all;
Promise.race = race;
Promise.resolve = resolve;
Promise.reject = reject;
Promise._setScheduler = setScheduler;
Promise._setAsap = setAsap;
Promise._asap = asap;

Promise.prototype = {
  constructor: Promise,

  /**
    The primary way of interacting with a promise is through its `then` method,
    which registers callbacks to receive either a promise's eventual value or the
    reason why the promise cannot be fulfilled.
  
    ```js
    findUser().then(function(user){
      // user is available
    }, function(reason){
      // user is unavailable, and you are given the reason why
    });
    ```
  
    Chaining
    --------
  
    The return value of `then` is itself a promise.  This second, 'downstream'
    promise is resolved with the return value of the first promise's fulfillment
    or rejection handler, or rejected if the handler throws an exception.
  
    ```js
    findUser().then(function (user) {
      return user.name;
    }, function (reason) {
      return 'default name';
    }).then(function (userName) {
      // If `findUser` fulfilled, `userName` will be the user's name, otherwise it
      // will be `'default name'`
    });
  
    findUser().then(function (user) {
      throw new Error('Found user, but still unhappy');
    }, function (reason) {
      throw new Error('`findUser` rejected and we're unhappy');
    }).then(function (value) {
      // never reached
    }, function (reason) {
      // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'.
      // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'.
    });
    ```
    If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream.
  
    ```js
    findUser().then(function (user) {
      throw new PedagogicalException('Upstream error');
    }).then(function (value) {
      // never reached
    }).then(function (value) {
      // never reached
    }, function (reason) {
      // The `PedgagocialException` is propagated all the way down to here
    });
    ```
  
    Assimilation
    ------------
  
    Sometimes the value you want to propagate to a downstream promise can only be
    retrieved asynchronously. This can be achieved by returning a promise in the
    fulfillment or rejection handler. The downstream promise will then be pending
    until the returned promise is settled. This is called *assimilation*.
  
    ```js
    findUser().then(function (user) {
      return findCommentsByAuthor(user);
    }).then(function (comments) {
      // The user's comments are now available
    });
    ```
  
    If the assimliated promise rejects, then the downstream promise will also reject.
  
    ```js
    findUser().then(function (user) {
      return findCommentsByAuthor(user);
    }).then(function (comments) {
      // If `findCommentsByAuthor` fulfills, we'll have the value here
    }, function (reason) {
      // If `findCommentsByAuthor` rejects, we'll have the reason here
    });
    ```
  
    Simple Example
    --------------
  
    Synchronous Example
  
    ```javascript
    let result;
  
    try {
      result = findResult();
      // success
    } catch(reason) {
      // failure
    }
    ```
  
    Errback Example
  
    ```js
    findResult(function(result, err){
      if (err) {
        // failure
      } else {
        // success
      }
    });
    ```
  
    Promise Example;
  
    ```javascript
    findResult().then(function(result){
      // success
    }, function(reason){
      // failure
    });
    ```
  
    Advanced Example
    --------------
  
    Synchronous Example
  
    ```javascript
    let author, books;
  
    try {
      author = findAuthor();
      books  = findBooksByAuthor(author);
      // success
    } catch(reason) {
      // failure
    }
    ```
  
    Errback Example
  
    ```js
  
    function foundBooks(books) {
  
    }
  
    function failure(reason) {
  
    }
  
    findAuthor(function(author, err){
      if (err) {
        failure(err);
        // failure
      } else {
        try {
          findBoooksByAuthor(author, function(books, err) {
            if (err) {
              failure(err);
            } else {
              try {
                foundBooks(books);
              } catch(reason) {
                failure(reason);
              }
            }
          });
        } catch(error) {
          failure(err);
        }
        // success
      }
    });
    ```
  
    Promise Example;
  
    ```javascript
    findAuthor().
      then(findBooksByAuthor).
      then(function(books){
        // found books
    }).catch(function(reason){
      // something went wrong
    });
    ```
  
    @method then
    @param {Function} onFulfilled
    @param {Function} onRejected
    Useful for tooling.
    @return {Promise}
  */
  then: then,

  /**
    `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same
    as the catch block of a try/catch statement.
  
    ```js
    function findAuthor(){
      throw new Error('couldn't find that author');
    }
  
    // synchronous
    try {
      findAuthor();
    } catch(reason) {
      // something went wrong
    }
  
    // async with promises
    findAuthor().catch(function(reason){
      // something went wrong
    });
    ```
  
    @method catch
    @param {Function} onRejection
    Useful for tooling.
    @return {Promise}
  */
  'catch': function _catch(onRejection) {
    return this.then(null, onRejection);
  }
};

function polyfill() {
    var local = undefined;

    if (typeof global !== 'undefined') {
        local = global;
    } else if (typeof self !== 'undefined') {
        local = self;
    } else {
        try {
            local = Function('return this')();
        } catch (e) {
            throw new Error('polyfill failed because global object is unavailable in this environment');
        }
    }

    var P = local.Promise;

    if (P) {
        var promiseToString = null;
        try {
            promiseToString = Object.prototype.toString.call(P.resolve());
        } catch (e) {
            // silently ignored
        }

        if (promiseToString === '[object Promise]' && !P.cast) {
            return;
        }
    }

    local.Promise = Promise;
}

// Strange compat..
Promise.polyfill = polyfill;
Promise.Promise = Promise;

return Promise;

})));
//# sourceMappingURL=es6-promise.map
;
;
/**
 * @description Adds touch event functionality related to swiping. Currently, only supports horizontal swiping.
 */
var Swipeable = (function (decouple) {
    var doc = window.document;
    var isScrolling = false;
    var scrollTimeout;
    var msPointerSupported = window.navigator.msPointerEnabled;
    var touch = {
        'start': msPointerSupported ? 'MSPointerDown' : 'touchstart',
        'cancel': 'touchcancel',
        'move': msPointerSupported ? 'MSPointerMove' : 'touchmove',
        'end': msPointerSupported ? 'MSPointerUp' : 'touchend'
    };

    /**
     * Swipeable constructor
     */
    function Swipeable(options) {
        options = options || {};

        // Set default values
        this.isSwiping = false;
        this.startX = null;
        this.startY = null;
        this.endX = null;
        this.endY = null;

        // Set options
        this.touchTarget = options.touchTarget;
        this.swipeLeftAction = options.swipeLeftAction || null;
        this.swipeRightAction = options.swipeRightAction || null;
        this.touchTargetMaxStartX = options.touchTargetMaxStartX || null;
        this.horizontalMoveTolerance = parseInt(options.horizontalMoveTolerance, 10) || 30;
        this.verticalMoveTolerance = parseInt(options.verticalMoveTolerance, 10) || 70;

        this.initTouchEvents();
    }

    Swipeable.prototype.swipeLeft = function() {
        if (typeof this.swipeLeftAction === 'function') {
            this.swipeLeftAction();
        }
        return this;
    }

    Swipeable.prototype.swipeRight = function () {
        if (typeof this.swipeRightAction === 'function') {
            this.swipeRightAction();
        }
        return this;
    }

    /**
     * Initializes touch event
     */
    Swipeable.prototype.initTouchEvents = function() {
        var self = this;

        /**
        * Throttle touch move processing during scroll events
        */
        this.onScroll = decouple(doc,
            'scroll',
            function () {
                if (!self.isSwiping) {
                    clearTimeout(scrollTimeout);
                    isScrolling = true;
                    scrollTimeout = setTimeout(function () {
                        isScrolling = false;
                    },
                        250);
                }
            });

        /**
         * Resets values on touchstart
         */
        this.resetTouch = function (eve) {
            if (typeof eve.touches === 'undefined') {
                return;
            }
            self.isSwiping = false;
            self.startX = eve.touches[0].pageX;
            self.startY = eve.touches[0].pageY;
        };

        this.touchTarget.addEventListener(touch.start, this.resetTouch);

        /**
         * Resets values on touchcancel -- support for limited devices that have touch cancelling events.
         */
        this.onTouchCancel = function () {
            self.isSwiping = false;
        };

        this.touchTarget.addEventListener(touch.cancel, this.onTouchCancel);

        /**
         * Toggles Swipeable on touchend
         */
        this.onTouchEnd = function (ev) {
            if (!ev || !ev.changedTouches) {
                return;
            }
            self.endX = ev.changedTouches[0].pageX;
            self.endY = ev.changedTouches[0].pageY;
            self.isSwiping = false;

            if (self.startX < self.touchTargetMaxStartX &&
                Math.abs(self.endY - self.startY) < self.verticalMoveTolerance) {
                var deltaX = self.endX - self.startX;
                if (Math.abs(deltaX) > self.horizontalMoveTolerance) {
                    if (deltaX < 0) {
                        self.swipeLeft();
                    } else {
                        self.swipeRight();
                    }
                }
            }
        };

        this.touchTarget.addEventListener(touch.end, this.onTouchEnd);

        /**
         * Throttle touch events that propagate to browser (helps with conflicting browser swipe history touch functionality)
         */
        this.onTouchMove = function (eve) {
            if (isScrolling || typeof eve.touches === 'undefined') {
                return;
            }

            var touchDeltaX = eve.touches[0].pageX - self.startX;

            if (Math.abs(touchDeltaX) > 20 || self.isSwiping) {
                self.isSwiping = true;
                if (self.startX < self.touchTargetMaxStartX) {
                    eve.preventDefault();
                }
            }
        };

        this.touchTarget.addEventListener(touch.move, this.onTouchMove);

        return this;
    };

    Swipeable.prototype.destroy = function () {
        // Remove event listeners
        this.touchTarget.removeEventListener(touch.start, this.resetTouch);
        this.touchTarget.removeEventListener(touch.cancel, this.onTouchCancel);
        this.touchTarget.removeEventListener(touch.end, this.onTouchEnd);
        this.touchTarget.removeEventListener(touch.move, this.onTouchMove);
        doc.removeEventListener('scroll', this.onScroll);

        this.swipeLeft = this.swipeRight = function () { };

        return this;
    }

    return Swipeable;
})((function () {
    var requestAnimFrame = (function () {
        return window.requestAnimationFrame ||
            window.webkitRequestAnimationFrame ||
            function (callback) {
                window.setTimeout(callback, 1000 / 60);
            };
    }());

    function decouple(node, event, fn) {
        var eve,
            tracking = false;

        function captureEvent(e) {
            eve = e;
            track();
        }

        function track() {
            if (!tracking) {
                requestAnimFrame(update);
                tracking = true;
            }
        }

        function update() {
            fn.call(node, eve);
            tracking = false;
        }

        node.addEventListener(event, captureEvent, false);

        return captureEvent;
    }

    /**
     * Expose decouple
     */
    return decouple;
})());;
/*! rangeslider.js - v2.3.0 | (c) 2016 @andreruffert | MIT license | https://github.com/andreruffert/rangeslider.js */
(function(factory) {
    'use strict';

    if (typeof define === 'function' && define.amd) {
        // AMD. Register as an anonymous module.
        define(['jquery'], factory);
    } else if (typeof exports === 'object') {
        // CommonJS
        module.exports = factory(require('jquery'));
    } else {
        // Browser globals
        factory(jQuery);
    }
}(function($) {
    'use strict';

    // Polyfill Number.isNaN(value)
    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN
    Number.isNaN = Number.isNaN || function(value) {
        return typeof value === 'number' && value !== value;
    };

    /**
     * Range feature detection
     * @return {Boolean}
     */
    function supportsRange() {
        var input = document.createElement('input');
        input.setAttribute('type', 'range');
        return input.type !== 'text';
    }

    var pluginName = 'rangeslider',
        pluginIdentifier = 0,
        hasInputRangeSupport = supportsRange(),
        defaults = {
            polyfill: true,
            orientation: 'horizontal',
            rangeClass: 'rangeslider',
            disabledClass: 'rangeslider--disabled',
            activeClass: 'rangeslider--active',
            horizontalClass: 'rangeslider--horizontal',
            verticalClass: 'rangeslider--vertical',
            fillClass: 'rangeslider__fill',
            handleClass: 'rangeslider__handle',
            startEvent: ['mousedown', 'touchstart', 'pointerdown'],
            moveEvent: ['mousemove', 'touchmove', 'pointermove'],
            endEvent: ['mouseup', 'touchend', 'pointerup']
        },
        constants = {
            orientation: {
                horizontal: {
                    dimension: 'width',
                    direction: 'left',
                    directionStyle: 'left',
                    coordinate: 'x'
                },
                vertical: {
                    dimension: 'height',
                    direction: 'top',
                    directionStyle: 'bottom',
                    coordinate: 'y'
                }
            }
        };

    /**
     * Delays a function for the given number of milliseconds, and then calls
     * it with the arguments supplied.
     *
     * @param  {Function} fn   [description]
     * @param  {Number}   wait [description]
     * @return {Function}
     */
    function delay(fn, wait) {
        var args = Array.prototype.slice.call(arguments, 2);
        return setTimeout(function(){ return fn.apply(null, args); }, wait);
    }

    /**
     * Returns a debounced function that will make sure the given
     * function is not triggered too much.
     *
     * @param  {Function} fn Function to debounce.
     * @param  {Number}   debounceDuration OPTIONAL. The amount of time in milliseconds for which we will debounce the function. (defaults to 100ms)
     * @return {Function}
     */
    function debounce(fn, debounceDuration) {
        debounceDuration = debounceDuration || 100;
        return function() {
            if (!fn.debouncing) {
                var args = Array.prototype.slice.apply(arguments);
                fn.lastReturnVal = fn.apply(window, args);
                fn.debouncing = true;
            }
            clearTimeout(fn.debounceTimeout);
            fn.debounceTimeout = setTimeout(function(){
                fn.debouncing = false;
            }, debounceDuration);
            return fn.lastReturnVal;
        };
    }

    /**
     * Check if a `element` is visible in the DOM
     *
     * @param  {Element}  element
     * @return {Boolean}
     */
    function isHidden(element) {
        return (
            element && (
                element.offsetWidth === 0 ||
                element.offsetHeight === 0 ||
                // Also Consider native `<details>` elements.
                element.open === false
            )
        );
    }

    /**
     * Get hidden parentNodes of an `element`
     *
     * @param  {Element} element
     * @return {[type]}
     */
    function getHiddenParentNodes(element) {
        var parents = [],
            node    = element.parentNode;

        while (isHidden(node)) {
            parents.push(node);
            node = node.parentNode;
        }
        return parents;
    }

    /**
     * Returns dimensions for an element even if it is not visible in the DOM.
     *
     * @param  {Element} element
     * @param  {String}  key     (e.g. offsetWidth …)
     * @return {Number}
     */
    function getDimension(element, key) {
        var hiddenParentNodes       = getHiddenParentNodes(element),
            hiddenParentNodesLength = hiddenParentNodes.length,
            inlineStyle             = [],
            dimension               = element[key];

        // Used for native `<details>` elements
        function toggleOpenProperty(element) {
            if (typeof element.open !== 'undefined') {
                element.open = (element.open) ? false : true;
            }
        }

        if (hiddenParentNodesLength) {
            for (var i = 0; i < hiddenParentNodesLength; i++) {

                // Cache style attribute to restore it later.
                inlineStyle[i] = hiddenParentNodes[i].style.cssText;

                // visually hide
                if (hiddenParentNodes[i].style.setProperty) {
                    hiddenParentNodes[i].style.setProperty('display', 'block', 'important');
                } else {
                    hiddenParentNodes[i].style.cssText += ';display: block !important';
                }
                hiddenParentNodes[i].style.height = '0';
                hiddenParentNodes[i].style.overflow = 'hidden';
                hiddenParentNodes[i].style.visibility = 'hidden';
                toggleOpenProperty(hiddenParentNodes[i]);
            }

            // Update dimension
            dimension = element[key];

            for (var j = 0; j < hiddenParentNodesLength; j++) {

                // Restore the style attribute
                hiddenParentNodes[j].style.cssText = inlineStyle[j];
                toggleOpenProperty(hiddenParentNodes[j]);
            }
        }
        return dimension;
    }

    /**
     * Returns the parsed float or the default if it failed.
     *
     * @param  {String}  str
     * @param  {Number}  defaultValue
     * @return {Number}
     */
    function tryParseFloat(str, defaultValue) {
        var value = parseFloat(str);
        return Number.isNaN(value) ? defaultValue : value;
    }

    /**
     * Capitalize the first letter of string
     *
     * @param  {String} str
     * @return {String}
     */
    function ucfirst(str) {
        return str.charAt(0).toUpperCase() + str.substr(1);
    }

    /**
     * Plugin
     * @param {String} element
     * @param {Object} options
     */
    function Plugin(element, options) {
        this.$window            = $(window);
        this.$document          = $(document);
        this.$element           = $(element);
        this.options            = $.extend( {}, defaults, options );
        this.polyfill           = this.options.polyfill;
        this.orientation        = this.$element[0].getAttribute('data-orientation') || this.options.orientation;
        this.onInit             = this.options.onInit;
        this.onSlide            = this.options.onSlide;
        this.onSlideEnd         = this.options.onSlideEnd;
        this.DIMENSION          = constants.orientation[this.orientation].dimension;
        this.DIRECTION          = constants.orientation[this.orientation].direction;
        this.DIRECTION_STYLE    = constants.orientation[this.orientation].directionStyle;
        this.COORDINATE         = constants.orientation[this.orientation].coordinate;

        // Plugin should only be used as a polyfill
        if (this.polyfill) {
            // Input range support?
            if (hasInputRangeSupport) { return false; }
        }

        this.identifier = 'js-' + pluginName + '-' +(pluginIdentifier++);
        this.startEvent = this.options.startEvent.join('.' + this.identifier + ' ') + '.' + this.identifier;
        this.moveEvent  = this.options.moveEvent.join('.' + this.identifier + ' ') + '.' + this.identifier;
        this.endEvent   = this.options.endEvent.join('.' + this.identifier + ' ') + '.' + this.identifier;
        this.toFixed    = (this.step + '').replace('.', '').length - 1;
        this.$fill      = $('<div class="' + this.options.fillClass + '" />');
        this.$handle    = $('<div class="' + this.options.handleClass + '" />');
        this.$range     = $('<div class="' + this.options.rangeClass + ' ' + this.options[this.orientation + 'Class'] + '" id="' + this.identifier + '" />').insertAfter(this.$element).prepend(this.$fill, this.$handle);

        // visually hide the input
        this.$element.css({
            'position': 'absolute',
            'width': '1px',
            'height': '1px',
            'overflow': 'hidden',
            'opacity': '0'
        });

        // Store context
        this.handleDown = $.proxy(this.handleDown, this);
        this.handleMove = $.proxy(this.handleMove, this);
        this.handleEnd  = $.proxy(this.handleEnd, this);

        this.init();

        // Attach Events
        var _this = this;
        this.$window.on('resize.' + this.identifier, debounce(function() {
            // Simulate resizeEnd event.
            delay(function() { _this.update(false, false); }, 300);
        }, 20));

        this.$document.on(this.startEvent, '#' + this.identifier + ':not(.' + this.options.disabledClass + ')', this.handleDown);

        // Listen to programmatic value changes
        this.$element.on('change.' + this.identifier, function(e, data) {
            if (data && data.origin === _this.identifier) {
                return;
            }

            var value = e.target.value,
                pos = _this.getPositionFromValue(value);
            _this.setPosition(pos);
        });
    }

    Plugin.prototype.init = function() {
        this.update(true, false);

        if (this.onInit && typeof this.onInit === 'function') {
            this.onInit();
        }
    };

    Plugin.prototype.update = function(updateAttributes, triggerSlide) {
        updateAttributes = updateAttributes || false;

        if (updateAttributes) {
            this.min    = tryParseFloat(this.$element[0].getAttribute('min'), 0);
            this.max    = tryParseFloat(this.$element[0].getAttribute('max'), 100);
            this.value  = tryParseFloat(this.$element[0].value, Math.round(this.min + (this.max-this.min)/2));
            this.step   = tryParseFloat(this.$element[0].getAttribute('step'), 1);
        }

        this.handleDimension    = getDimension(this.$handle[0], 'offset' + ucfirst(this.DIMENSION));
        this.rangeDimension     = getDimension(this.$range[0], 'offset' + ucfirst(this.DIMENSION));
        this.maxHandlePos       = this.rangeDimension - this.handleDimension;
        this.grabPos            = this.handleDimension / 2;
        this.position           = this.getPositionFromValue(this.value);

        // Consider disabled state
        if (this.$element[0].disabled) {
            this.$range.addClass(this.options.disabledClass);
        } else {
            this.$range.removeClass(this.options.disabledClass);
        }

        this.setPosition(this.position, triggerSlide);
    };

    Plugin.prototype.handleDown = function(e) {
        e.preventDefault();
        this.$document.on(this.moveEvent, this.handleMove);
        this.$document.on(this.endEvent, this.handleEnd);

        // add active class because Firefox is ignoring
        // the handle:active pseudo selector because of `e.preventDefault();`
        this.$range.addClass(this.options.activeClass);

        // If we click on the handle don't set the new position
        if ((' ' + e.target.className + ' ').replace(/[\n\t]/g, ' ').indexOf(this.options.handleClass) > -1) {
            return;
        }

        var pos         = this.getRelativePosition(e),
            rangePos    = this.$range[0].getBoundingClientRect()[this.DIRECTION],
            handlePos   = this.getPositionFromNode(this.$handle[0]) - rangePos,
            setPos      = (this.orientation === 'vertical') ? (this.maxHandlePos - (pos - this.grabPos)) : (pos - this.grabPos);

        this.setPosition(setPos);

        if (pos >= handlePos && pos < handlePos + this.handleDimension) {
            this.grabPos = pos - handlePos;
        }
    };

    Plugin.prototype.handleMove = function(e) {
        e.preventDefault();
        var pos = this.getRelativePosition(e);
        var setPos = (this.orientation === 'vertical') ? (this.maxHandlePos - (pos - this.grabPos)) : (pos - this.grabPos);
        this.setPosition(setPos);
    };

    Plugin.prototype.handleEnd = function(e) {
        e.preventDefault();
        this.$document.off(this.moveEvent, this.handleMove);
        this.$document.off(this.endEvent, this.handleEnd);

        this.$range.removeClass(this.options.activeClass);

        // Ok we're done fire the change event
        this.$element.trigger('change', { origin: this.identifier });

        if (this.onSlideEnd && typeof this.onSlideEnd === 'function') {
            this.onSlideEnd(this.position, this.value);
        }
    };

    Plugin.prototype.cap = function(pos, min, max) {
        if (pos < min) { return min; }
        if (pos > max) { return max; }
        return pos;
    };

    Plugin.prototype.setPosition = function(pos, triggerSlide) {
        var value, newPos;

        if (triggerSlide === undefined) {
            triggerSlide = true;
        }

        // Snapping steps
        value = this.getValueFromPosition(this.cap(pos, 0, this.maxHandlePos));
        newPos = this.getPositionFromValue(value);

        // Update ui
        this.$fill[0].style[this.DIMENSION] = (newPos + this.grabPos) + 'px';
        this.$handle[0].style[this.DIRECTION_STYLE] = newPos + 'px';
        this.setValue(value);

        // Update globals
        this.position = newPos;
        this.value = value;

        if (triggerSlide && this.onSlide && typeof this.onSlide === 'function') {
            this.onSlide(newPos, value);
        }
    };

    // Returns element position relative to the parent
    Plugin.prototype.getPositionFromNode = function(node) {
        var i = 0;
        while (node !== null) {
            i += node.offsetLeft;
            node = node.offsetParent;
        }
        return i;
    };

    Plugin.prototype.getRelativePosition = function(e) {
        // Get the offset DIRECTION relative to the viewport
        var ucCoordinate = ucfirst(this.COORDINATE),
            rangePos = this.$range[0].getBoundingClientRect()[this.DIRECTION],
            pageCoordinate = 0;

        if (typeof e.originalEvent['client' + ucCoordinate] !== 'undefined') {
            pageCoordinate = e.originalEvent['client' + ucCoordinate];
        }
        else if (
          e.originalEvent.touches &&
          e.originalEvent.touches[0] &&
          typeof e.originalEvent.touches[0]['client' + ucCoordinate] !== 'undefined'
        ) {
            pageCoordinate = e.originalEvent.touches[0]['client' + ucCoordinate];
        }
        else if(e.currentPoint && typeof e.currentPoint[this.COORDINATE] !== 'undefined') {
            pageCoordinate = e.currentPoint[this.COORDINATE];
        }

        return pageCoordinate - rangePos;
    };

    Plugin.prototype.getPositionFromValue = function(value) {
        var percentage, pos;
        percentage = (value - this.min)/(this.max - this.min);
        pos = (!Number.isNaN(percentage)) ? percentage * this.maxHandlePos : 0;
        return pos;
    };

    Plugin.prototype.getValueFromPosition = function(pos) {
        var percentage, value;
        percentage = ((pos) / (this.maxHandlePos || 1));
        value = this.step * Math.round(percentage * (this.max - this.min) / this.step) + this.min;
        return Number((value).toFixed(this.toFixed));
    };

    Plugin.prototype.setValue = function(value) {
        if (value === this.value && this.$element[0].value !== '') {
            return;
        }

        // Set the new value and fire the `input` event
        this.$element
            .val(value)
            .trigger('input', { origin: this.identifier });
    };

    Plugin.prototype.destroy = function() {
        this.$document.off('.' + this.identifier);
        this.$window.off('.' + this.identifier);

        this.$element
            .off('.' + this.identifier)
            .removeAttr('style')
            .removeData('plugin_' + pluginName);

        // Remove the generated markup
        if (this.$range && this.$range.length) {
            this.$range[0].parentNode.removeChild(this.$range[0]);
        }
    };

    // A really lightweight plugin wrapper around the constructor,
    // preventing against multiple instantiations
    $.fn[pluginName] = function(options) {
        var args = Array.prototype.slice.call(arguments, 1);

        return this.each(function() {
            var $this = $(this),
                data  = $this.data('plugin_' + pluginName);

            // Create a new instance.
            if (!data) {
                $this.data('plugin_' + pluginName, (data = new Plugin(this, options)));
            }

            // Make it possible to access methods from public.
            // e.g `$element.rangeslider('method');`
            if (typeof options === 'string') {
                data[options].apply(data, args);
            }
        });
    };

    return 'rangeslider.js is available in jQuery context e.g $(selector).rangeslider(options);';

}));
;
window.EdelweissWholesalers = window.EdelweisssWholesalers || {};

EdelweissWholesalers.createWholesalerTable = function (sku) {
    if (sku) {
        var getAvailabilityPromise = EdelweissWholesalers.getWholesalerAvailabilityData(sku);
        getAvailabilityPromise.then(function (data) {
            var wholesalerGrid = $("#wholesalerGrid");
            EdelweissWholesalers.applyInitialDataTableSettingsForWholesaler(wholesalerGrid);
            var wholesalerTable = null;
            wholesalerTable = wholesalerGrid.dataTable({
                "dom": "t",
                "destroy": true,
                "columns": data.aoColumns,
                "data": data.aaData,
                "bAutoWidth": false,
                "pageLength": 50
            });
        });
    }
    else {
        return;
    }
};

 EdelweissWholesalers.getWholesalerAvailabilityData = function (sku) {
    var apiUrl = "/api/wholesalers/availability/" + sku;
    var getAvailabilityPromise = new Promise(function (resolve, reject) {
        $.ajax({
            type: "POST",
            url: apiUrl,
            success: function (data) {
                resolve(data);
            },
            error: function () {
                alert("there is an error to get wholesaler availability data!");
            },
            datatype: "json"
        });
    });
    return getAvailabilityPromise;
};

EdelweissWholesalers.applyInitialDataTableSettingsForWholesaler = function (wholesalerGrid) {
     $.fn.dataTableExt.oStdClasses.sStripeEven = "wFil tlList even altRow";
     $.fn.dataTableExt.oStdClasses.sStripeOdd = "wFil tlList odd stdRow";
     wholesalerGrid.empty();
 };


;
var CascadingMenu = function (menuId, nameOfFunctionToRunOnSelect) {
    this.menuId = menuId;
    /* this function must be accesible via the global scope */
    this.nameOfFunctionToRunOnSelect = nameOfFunctionToRunOnSelect;
    this.entireMenuSelector = $("#" + menuId + " .cascadingMenu");
    this.entireMenuToggleIcon = $("#" + menuId + " .cascadingMenuToggleIcon");
}

CascadingMenu.prototype.initialize = function () {
    this.initializeOutsideClickListener();
    this.initializeToggleSubMenuListener();
    this.initializeToggleEntireMenuListener();
};

CascadingMenu.prototype.initializeOutsideClickListener = function() {
    var self = this;
    $(document).mouseup(function (e) {
        var container = $("#" + self.menuId);
        // if the target of the click isn't the container nor a descendant of the container
        if (!container.is(e.target) && container.has(e.target).length === 0) {
            self.entireMenuSelector.hide();
            self.entireMenuToggleIcon.removeClass("icon-drop-up-icon-01");
            self.entireMenuToggleIcon.addClass("icon-drop-down-icon");
        }
    });
}

CascadingMenu.prototype.initializeToggleSubMenuListener = function () {
    var self = this;
    $("#" + self.menuId + " .cascadingMenuItem").on("click", function () {
        var hasChildren = $(this).attr("data-haschildren") === "true";
        var listId = $(this).attr("data-listid");
        if (hasChildren) {
            self.toggleSubMenu(listId);
        } else {
            var listValue = $(this).attr("data-listvalue");
            self.setSelectedValue(listId, listValue);
            window[nameOfFunctionToRunOnSelect](listId);
            self.entireMenuSelector.hide();
            self.entireMenuToggleIcon.removeClass("icon-drop-up-icon-01");
            self.entireMenuToggleIcon.addClass("icon-drop-down-icon");
        }
    });
};

CascadingMenu.prototype.initializeToggleEntireMenuListener = function () {
    var self = this;
    $("#" + this.menuId + " .cascadingMenuToggleIcon").on("click", function () {
        self.toggleEntireMenu();
    });
};

CascadingMenu.prototype.toggleEntireMenu = function () {
    if (this.entireMenuSelector.is(":visible")) {
        this.entireMenuSelector.hide();
        this.entireMenuToggleIcon.removeClass("icon-drop-up-icon-01");
        this.entireMenuToggleIcon.addClass("icon-drop-down-icon");
    } else {
        this.entireMenuSelector.show();
        this.entireMenuToggleIcon.removeClass("icon-drop-down-icon");
        this.entireMenuToggleIcon.addClass("icon-drop-up-icon-01");
    }
};

CascadingMenu.prototype.toggleSubMenu = function (listId) {
    var subMenuSelector = $("#" + this.menuId + " .cascadingSubMenu[data-listid='" + listId + "']");
    var subMenuToggleIcon = $("#" + this.menuId + " .cascadingMenuItem[data-listid='" + listId + "'] .cascadingSubMenuToggleIcon");
    if (subMenuSelector.is(":visible")) {
        subMenuSelector.hide();
        subMenuToggleIcon.removeClass("icon-drop-up-icon-01");
        subMenuToggleIcon.addClass("icon-drop-down-icon");
    } else {
        this.hideAllSubMenus();
        subMenuSelector.show();
        subMenuToggleIcon.removeClass("icon-drop-down-icon");
        subMenuToggleIcon.addClass("icon-drop-up-icon-01");
    }
};

CascadingMenu.prototype.setSelectedValue = function (listId, listValue) {
    $("#" + this.menuId).attr("val", listId);
    $("#" + this.menuId + " .cascadingMenuSelectedValue").html(listValue);
};

CascadingMenu.prototype.hideAllSubMenus = function () {
    $("#" + this.menuId + " .cascadingSubMenu").hide();
    $("#" + this.menuId + " .cascadingSubMenuToggleIcon").removeClass("icon-drop-up-icon-01");
    $("#" + this.menuId + " .cascadingSubMenuToggleIcon").addClass("icon-drop-down-icon");
};;
window.EdelweissPeerData = window.EdelweissPeerData || {};
EdelweissPeerData.listTypes = {
    ListView: 1,
    TwoColumn: 7
};
EdelweissPeerData.itemTypes = {
    Title: 1,
    Review: 50
};
EdelweissPeerData.isPeerDataShown = {};
EdelweissPeerData.peerDataStatusKeys = {
    TwoColumnTitleView: EdelweissPeerData.listTypes.TwoColumn + "_" + EdelweissPeerData.itemTypes.Title,
    TwoColumnReview: EdelweissPeerData.listTypes.TwoColumn + "_" + EdelweissPeerData.itemTypes.Review,
    OneColumnTitleView: EdelweissPeerData.listTypes.ListView + "_" + EdelweissPeerData.itemTypes.Title
}

EdelweissPeerData.peerDataSortColumns = ["208", "211", "213"]  // PercentOrdered, PercentOwned, PercentSold

EdelweissPeerData.isPeerDataShown[EdelweissPeerData.peerDataStatusKeys.TwoColumnReview] = false;
EdelweissPeerData.isPeerDataShown[EdelweissPeerData.peerDataStatusKeys.TwoColumnTitleView] = false;
EdelweissPeerData.isPeerDataShown[EdelweissPeerData.peerDataStatusKeys.OneColumnTitleView] = false;


EdelweissPeerData.openPeerOptionsPopover = function (event, sku, market, monthsBack, storeId) {
    event.stopPropagation();
    var target = "#marketHeader_" + sku;
    var url = "/GetTreelineControl.aspx?controlName=/uc/analytics/PopoverPeerDataOptions.ascx";
    var peerParams = {
        selectedMarket: market,
        sku: sku,
        selectedTimeFrame: monthsBack,
        selectedStoreId: storeId
    }
    url += "&" + $.param(peerParams);

    $(target).webuiPopover({
        trigger: "click",
        placement: "bottom-right",
        width: 250,
        type: "async",
        url: url,
        cache: false,
        dismissible: true,
        closeable: true,
        multi: true,
        onHide: function ($element) {
            var $peerOptionsContainer = $(".peerDataOptionsContainer[data-sku=" + sku + "]");
            $peerOptionsContainer.hide();
        }

    });

    if (!$(target).hasClass("popoverFirstShowed")) {
        $(target).webuiPopover('show');
        $(target).addClass("popoverFirstShowed");
    }
              
};

EdelweissPeerData.openLocationSelectorPopover = function (event, sku, storeId, market, monthsBack, titleContent) {
    event.stopPropagation();
    $("#locationSelector_" + sku).attr("title", "");
    var target = "#locationSelector_" + sku;
    var url = "/GetTreelineControl.aspx?controlName=/uc/analytics/PopoverLocationSelector.ascx";
    var params = {        
        sku: sku,
        selectedStoreId: storeId,
        selectedMarket: market,
        selectedTimeFrame: monthsBack
    }
    url += "&" + $.param(params);

    $(target).webuiPopover({
        trigger: "click",
        placement: "bottom-left",
        width: 250,
        type: "async",
        url: url,
        title: '',
        cache: false,
        dismissible: true,
        multi: true,
        onHide: function ($element) {
            $(".peerData_locationSelector").attr("title", titleContent);
            var $locationOptionsContainer = $(".locationOptionsContainer[data-sku=" + sku + "]");
            $locationOptionsContainer.hide();
        }

    });

    if (!$(target).hasClass("popoverFirstShowed")) {
        $(target).webuiPopover('show');
        $(target).addClass("popoverFirstShowed");
    }

};

EdelweissPeerData.changePeerGroup = function (peerMarket, monthsBack, storeId, peerGroupPrefName) {
    $(".peerGroupOption").addClass("box_unchecked").removeClass("box_checked");
    $("#peerGroup_" + peerMarket).addClass("box_checked");

    async.series([
        async.apply(EdelweissAnalytics.saveAnalyticsUserPreference,
           peerGroupPrefName, peerMarket)
    ], function (err, results) {
        if (err) {
            console.log("Failed to save list view market preference.");
        } else {
            $(".marketHeader").webuiPopover('hide');
            var resultType = getListViewProperty("resultType");
            var listType = getListViewProperty("listType");
            var itemType = getListViewProperty("itemType");
            var sortColumn = getSortOrd().toString();
            if (itemType === getEnumValue('itemType', 'TITLE')) {
                $(".list_header").empty();
                $(".list_header").load("/GetTreelineControl.aspx?controlName=/uc/listviews/menus/ListView_TopMenu.ascx&ResultType=" + resultType + "&SelectedViewType=" + listType + "&ItemType=" + itemType);
            }            
            if (_.includes(EdelweissPeerData.peerDataSortColumns, sortColumn)) {
                reloadList();
            } else {
                EdelweissPeerData.updatePeerDataBarsOnPage(peerMarket, monthsBack, storeId);
            }
        }
    });
};

EdelweissPeerData.changeTimeFrame = function (monthsBack, market, storeId, timeFramePrefName) {
    $(".peerTimeFrameOption").addClass("box_unchecked").removeClass("box_checked");
    $("#peerTimeFrame_" + monthsBack).addClass("box_checked");

    async.series([
        async.apply(EdelweissAnalytics.saveAnalyticsUserPreference,
           timeFramePrefName, monthsBack)
    ], function (err, results) {
        if (err) {
            console.log("Failed to save list view time frame preference.");
        } else {
            $(".marketHeader").webuiPopover('hide');
            var sortColumn = getSortOrd().toString();
            if (_.includes(EdelweissPeerData.peerDataSortColumns, sortColumn)) {
                reloadList();
            } else {
                EdelweissPeerData.updatePeerDataBarsOnPage(market, monthsBack, storeId);
            }
        }
    });
}

EdelweissPeerData.changeLocation = function (storeId, market, monthsBack, locationPrefName) {
    async.series([
        async.apply(EdelweissAnalytics.saveAnalyticsUserPreference,
           locationPrefName, storeId)
    ], function (err, results) {
        if (err) {
            console.log("Failed to save list view location preference.");
        } else {
            $(".peerData_locationSelector").webuiPopover('hide');
            var sortColumn = getSortOrd().toString();
            if (_.includes(EdelweissPeerData.peerDataSortColumns, sortColumn)) {
                reloadList();
            } else {
                EdelweissPeerData.updatePeerDataBarsOnPage(market, monthsBack, storeId);
            }
        }
    });

};

EdelweissPeerData.ShowPeerData = function (isPeerDataHiddenPrefName, listTypeView, itemType) {  
    $(".analyticsPeerData_Barwrap").show();
    var peerDataStatusKey = listTypeView + "_" + itemType;
    EdelweissPeerData.isPeerDataShown[peerDataStatusKey] = true;
    EdelweissAnalytics.saveAnalyticsUserPreference(isPeerDataHiddenPrefName, "disable", null);
}

EdelweissPeerData.hidePeerData = function (isPeerDataHiddenPrefName, listTypeView, itemType) {   
    $('.analyticsPeerData_Barwrap').hide();
    var peerDataStatusKey = listTypeView + "_" + itemType;
    EdelweissPeerData.isPeerDataShown[peerDataStatusKey] = false;
    EdelweissAnalytics.saveAnalyticsUserPreference(isPeerDataHiddenPrefName, "true", null);
}

EdelweissPeerData.updatePeerDataBarsOnPage = function (market, monthsBack, storeId) {
    var itemType = getListViewProperty("itemType");
    var listType = getListViewProperty("listType");
    var skusOnCurrentPage = getSkusOnCurrentPage(itemType);
    loadPeerDataSectionAndUpdateCheckmarks(skusOnCurrentPage, market, monthsBack, storeId, listType);
}

function loadPeerDataSectionAndUpdateCheckmarks(skuList, market, monthsBack, storeId, listType) {   
    for (var i = 0; i < skuList.length; i++) {
        var sku = skuList[i];
        loadPeerDataBarForSkuAndUpdateCheckmark(sku, market, monthsBack, storeId, listType);        
    }
}

function loadPeerDataBarForSkuAndUpdateCheckmark(sku, market, monthsBack, storeId, listType) {
    var peerDataParams = {
        sku: sku,
        market: market,
        monthsBack: monthsBack,
        storeId: storeId,
        listType: listType
    }
    $("#analyticsPeerData_" + sku).empty();    
    $("#analyticsPeerData_" + sku).load("/GetTreelineControl.aspx?controlName=/uc/analytics/PeerDataListView.ascx&" + $.param(peerDataParams), function (response, status, xhr) {
        if (status == "error") {
            alert("there is an error to load peer data bar!");
        } else {
            updateCheckmarkOnJacketCover(sku);
            updateSortRefine(sku);
        }
    });
}

function updateCheckmarkOnJacketCover(sku) {
    var stockAnalysisObj = getOnOrderAndOnHandValues(sku);
    updateJacketCoverCheckmark(sku, stockAnalysisObj.onHand, stockAnalysisObj.onOrder);
}

function getOnOrderAndOnHandValues(sku) {
    var onOrderString = $("#pdContainer_" + sku).find(".storeOnOrderNumber").text();
    var onHandString = $("#pdContainer_" + sku).find(".storeOnHandNumber").text();
    var onOrder = parseInt(onOrderString) || 0;
    var onHand = parseInt(onHandString) || 0;
    var stockAnalysisObj = {
        onOrder: onOrder,
        onHand: onHand
    };
    return stockAnalysisObj;
}

function updateSortRefine(sku) {
    var stockAnalysisObj = getOnOrderAndOnHandValues(sku);
    if (window.sortrefine && window.sortrefine.length > 0) {
        var indexes = $.map(window.sortrefine, function (obj, index) {
            if (obj.item == sku) {
                return index;
            }
        });
        var firstIndex = indexes.length > 0 ? indexes[0] : 0;
        window.sortrefine[firstIndex].StoreOnHand = stockAnalysisObj.onHand;
        window.sortrefine[firstIndex].StoreOnOrder = stockAnalysisObj.onOrder;
    }
}
;
/*
 * jQuery Iframe Transport Plugin
 * https://github.com/blueimp/jQuery-File-Upload
 *
 * Copyright 2011, Sebastian Tschan
 * https://blueimp.net
 *
 * Licensed under the MIT license:
 * https://opensource.org/licenses/MIT
 */

/* global define, require, window, document, JSON */

;(function (factory) {
    'use strict';
    if (typeof define === 'function' && define.amd) {
        // Register as an anonymous AMD module:
        define(['jquery'], factory);
    } else if (typeof exports === 'object') {
        // Node/CommonJS:
        factory(require('jquery'));
    } else {
        // Browser globals:
        factory(window.jQuery);
    }
}(function ($) {
    'use strict';

    // Helper variable to create unique names for the transport iframes:
    var counter = 0,
        jsonAPI = $,
        jsonParse = 'parseJSON';

    if ('JSON' in window && 'parse' in JSON) {
      jsonAPI = JSON;
      jsonParse = 'parse';
    }

    // The iframe transport accepts four additional options:
    // options.fileInput: a jQuery collection of file input fields
    // options.paramName: the parameter name for the file form data,
    //  overrides the name property of the file input field(s),
    //  can be a string or an array of strings.
    // options.formData: an array of objects with name and value properties,
    //  equivalent to the return data of .serializeArray(), e.g.:
    //  [{name: 'a', value: 1}, {name: 'b', value: 2}]
    // options.initialIframeSrc: the URL of the initial iframe src,
    //  by default set to "javascript:false;"
    $.ajaxTransport('iframe', function (options) {
        if (options.async) {
            // javascript:false as initial iframe src
            // prevents warning popups on HTTPS in IE6:
            /*jshint scripturl: true */
            var initialIframeSrc = options.initialIframeSrc || 'javascript:false;',
            /*jshint scripturl: false */
                form,
                iframe,
                addParamChar;
            return {
                send: function (_, completeCallback) {
                    form = $('<form style="display:none;"></form>');
                    form.attr('accept-charset', options.formAcceptCharset);
                    addParamChar = /\?/.test(options.url) ? '&' : '?';
                    // XDomainRequest only supports GET and POST:
                    if (options.type === 'DELETE') {
                        options.url = options.url + addParamChar + '_method=DELETE';
                        options.type = 'POST';
                    } else if (options.type === 'PUT') {
                        options.url = options.url + addParamChar + '_method=PUT';
                        options.type = 'POST';
                    } else if (options.type === 'PATCH') {
                        options.url = options.url + addParamChar + '_method=PATCH';
                        options.type = 'POST';
                    }
                    // IE versions below IE8 cannot set the name property of
                    // elements that have already been added to the DOM,
                    // so we set the name along with the iframe HTML markup:
                    counter += 1;
                    iframe = $(
                        '<iframe src="' + initialIframeSrc +
                            '" name="iframe-transport-' + counter + '"></iframe>'
                    ).bind('load', function () {
                        var fileInputClones,
                            paramNames = $.isArray(options.paramName) ?
                                    options.paramName : [options.paramName];
                        iframe
                            .unbind('load')
                            .bind('load', function () {
                                var response;
                                // Wrap in a try/catch block to catch exceptions thrown
                                // when trying to access cross-domain iframe contents:
                                try {
                                    response = iframe.contents();
                                    // Google Chrome and Firefox do not throw an
                                    // exception when calling iframe.contents() on
                                    // cross-domain requests, so we unify the response:
                                    if (!response.length || !response[0].firstChild) {
                                        throw new Error();
                                    }
                                } catch (e) {
                                    response = undefined;
                                }
                                // The complete callback returns the
                                // iframe content document as response object:
                                completeCallback(
                                    200,
                                    'success',
                                    {'iframe': response}
                                );
                                // Fix for IE endless progress bar activity bug
                                // (happens on form submits to iframe targets):
                                $('<iframe src="' + initialIframeSrc + '"></iframe>')
                                    .appendTo(form);
                                window.setTimeout(function () {
                                    // Removing the form in a setTimeout call
                                    // allows Chrome's developer tools to display
                                    // the response result
                                    form.remove();
                                }, 0);
                            });
                        form
                            .prop('target', iframe.prop('name'))
                            .prop('action', options.url)
                            .prop('method', options.type);
                        if (options.formData) {
                            $.each(options.formData, function (index, field) {
                                $('<input type="hidden"/>')
                                    .prop('name', field.name)
                                    .val(field.value)
                                    .appendTo(form);
                            });
                        }
                        if (options.fileInput && options.fileInput.length &&
                                options.type === 'POST') {
                            fileInputClones = options.fileInput.clone();
                            // Insert a clone for each file input field:
                            options.fileInput.after(function (index) {
                                return fileInputClones[index];
                            });
                            if (options.paramName) {
                                options.fileInput.each(function (index) {
                                    $(this).prop(
                                        'name',
                                        paramNames[index] || options.paramName
                                    );
                                });
                            }
                            // Appending the file input fields to the hidden form
                            // removes them from their original location:
                            form
                                .append(options.fileInput)
                                .prop('enctype', 'multipart/form-data')
                                // enctype must be set as encoding for IE:
                                .prop('encoding', 'multipart/form-data');
                            // Remove the HTML5 form attribute from the input(s):
                            options.fileInput.removeAttr('form');
                        }
                        form.submit();
                        // Insert the file input fields at their original location
                        // by replacing the clones with the originals:
                        if (fileInputClones && fileInputClones.length) {
                            options.fileInput.each(function (index, input) {
                                var clone = $(fileInputClones[index]);
                                // Restore the original name and form properties:
                                $(input)
                                    .prop('name', clone.prop('name'))
                                    .attr('form', clone.attr('form'));
                                clone.replaceWith(input);
                            });
                        }
                    });
                    form.append(iframe).appendTo(document.body);
                },
                abort: function () {
                    if (iframe) {
                        // javascript:false as iframe src aborts the request
                        // and prevents warning popups on HTTPS in IE6.
                        // concat is used to avoid the "Script URL" JSLint error:
                        iframe
                            .unbind('load')
                            .prop('src', initialIframeSrc);
                    }
                    if (form) {
                        form.remove();
                    }
                }
            };
        }
    });

    // The iframe transport returns the iframe content document as response.
    // The following adds converters from iframe to text, json, html, xml
    // and script.
    // Please note that the Content-Type for JSON responses has to be text/plain
    // or text/html, if the browser doesn't include application/json in the
    // Accept header, else IE will show a download dialog.
    // The Content-Type for XML responses on the other hand has to be always
    // application/xml or text/xml, so IE properly parses the XML response.
    // See also
    // https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation
    $.ajaxSetup({
        converters: {
            'iframe text': function (iframe) {
                return iframe && $(iframe[0].body).text();
            },
            'iframe json': function (iframe) {
                return iframe && jsonAPI[jsonParse]($(iframe[0].body).text());
            },
            'iframe html': function (iframe) {
                return iframe && $(iframe[0].body).html();
            },
            'iframe xml': function (iframe) {
                var xmlDoc = iframe && iframe[0];
                return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc :
                        $.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) ||
                            $(xmlDoc.body).html());
            },
            'iframe script': function (iframe) {
                return iframe && $.globalEval($(iframe[0].body).text());
            }
        }
    });

}));
;
/*
 * jQuery File Upload Plugin
 * https://github.com/blueimp/jQuery-File-Upload
 *
 * Copyright 2010, Sebastian Tschan
 * https://blueimp.net
 *
 * Licensed under the MIT license:
 * https://opensource.org/licenses/MIT
 */

/* jshint nomen:false */
/* global define, require, window, document, location, Blob, FormData */

;(function (factory) {
    'use strict';
    if (typeof define === 'function' && define.amd) {
        // Register as an anonymous AMD module:
        define([
            'jquery',
            'jquery-ui/ui/widget'
        ], factory);
    } else if (typeof exports === 'object') {
        // Node/CommonJS:
        factory(
            require('jquery'),
            require('./vendor/jquery.ui.widget')
        );
    } else {
        // Browser globals:
        factory(window.jQuery);
    }
}(function ($) {
    'use strict';

    // Detect file input support, based on
    // http://viljamis.com/blog/2012/file-upload-support-on-mobile/
    $.support.fileInput = !(new RegExp(
        // Handle devices which give false positives for the feature detection:
        '(Android (1\\.[0156]|2\\.[01]))' +
            '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' +
            '|(w(eb)?OSBrowser)|(webOS)' +
            '|(Kindle/(1\\.0|2\\.[05]|3\\.0))'
    ).test(window.navigator.userAgent) ||
        // Feature detection for all other devices:
        $('<input type="file">').prop('disabled'));

    // The FileReader API is not actually used, but works as feature detection,
    // as some Safari versions (5?) support XHR file uploads via the FormData API,
    // but not non-multipart XHR file uploads.
    // window.XMLHttpRequestUpload is not available on IE10, so we check for
    // window.ProgressEvent instead to detect XHR2 file upload capability:
    $.support.xhrFileUpload = !!(window.ProgressEvent && window.FileReader);
    $.support.xhrFormDataFileUpload = !!window.FormData;

    // Detect support for Blob slicing (required for chunked uploads):
    $.support.blobSlice = window.Blob && (Blob.prototype.slice ||
        Blob.prototype.webkitSlice || Blob.prototype.mozSlice);

    // Helper function to create drag handlers for dragover/dragenter/dragleave:
    function getDragHandler(type) {
        var isDragOver = type === 'dragover';
        return function (e) {
            e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
            var dataTransfer = e.dataTransfer;
            if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 &&
                    this._trigger(
                        type,
                        $.Event(type, {delegatedEvent: e})
                    ) !== false) {
                e.preventDefault();
                if (isDragOver) {
                    dataTransfer.dropEffect = 'copy';
                }
            }
        };
    }

    // The fileupload widget listens for change events on file input fields defined
    // via fileInput setting and paste or drop events of the given dropZone.
    // In addition to the default jQuery Widget methods, the fileupload widget
    // exposes the "add" and "send" methods, to add or directly send files using
    // the fileupload API.
    // By default, files added via file input selection, paste, drag & drop or
    // "add" method are uploaded immediately, but it is possible to override
    // the "add" callback option to queue file uploads.
    $.widget('blueimp.fileupload', {

        options: {
            // The drop target element(s), by the default the complete document.
            // Set to null to disable drag & drop support:
            dropZone: $(document),
            // The paste target element(s), by the default undefined.
            // Set to a DOM node or jQuery object to enable file pasting:
            pasteZone: undefined,
            // The file input field(s), that are listened to for change events.
            // If undefined, it is set to the file input fields inside
            // of the widget element on plugin initialization.
            // Set to null to disable the change listener.
            fileInput: undefined,
            // By default, the file input field is replaced with a clone after
            // each input field change event. This is required for iframe transport
            // queues and allows change events to be fired for the same file
            // selection, but can be disabled by setting the following option to false:
            replaceFileInput: true,
            // The parameter name for the file form data (the request argument name).
            // If undefined or empty, the name property of the file input field is
            // used, or "files[]" if the file input name property is also empty,
            // can be a string or an array of strings:
            paramName: undefined,
            // By default, each file of a selection is uploaded using an individual
            // request for XHR type uploads. Set to false to upload file
            // selections in one request each:
            singleFileUploads: true,
            // To limit the number of files uploaded with one XHR request,
            // set the following option to an integer greater than 0:
            limitMultiFileUploads: undefined,
            // The following option limits the number of files uploaded with one
            // XHR request to keep the request size under or equal to the defined
            // limit in bytes:
            limitMultiFileUploadSize: undefined,
            // Multipart file uploads add a number of bytes to each uploaded file,
            // therefore the following option adds an overhead for each file used
            // in the limitMultiFileUploadSize configuration:
            limitMultiFileUploadSizeOverhead: 512,
            // Set the following option to true to issue all file upload requests
            // in a sequential order:
            sequentialUploads: false,
            // To limit the number of concurrent uploads,
            // set the following option to an integer greater than 0:
            limitConcurrentUploads: undefined,
            // Set the following option to true to force iframe transport uploads:
            forceIframeTransport: false,
            // Set the following option to the location of a redirect url on the
            // origin server, for cross-domain iframe transport uploads:
            redirect: undefined,
            // The parameter name for the redirect url, sent as part of the form
            // data and set to 'redirect' if this option is empty:
            redirectParamName: undefined,
            // Set the following option to the location of a postMessage window,
            // to enable postMessage transport uploads:
            postMessage: undefined,
            // By default, XHR file uploads are sent as multipart/form-data.
            // The iframe transport is always using multipart/form-data.
            // Set to false to enable non-multipart XHR uploads:
            multipart: true,
            // To upload large files in smaller chunks, set the following option
            // to a preferred maximum chunk size. If set to 0, null or undefined,
            // or the browser does not support the required Blob API, files will
            // be uploaded as a whole.
            maxChunkSize: undefined,
            // When a non-multipart upload or a chunked multipart upload has been
            // aborted, this option can be used to resume the upload by setting
            // it to the size of the already uploaded bytes. This option is most
            // useful when modifying the options object inside of the "add" or
            // "send" callbacks, as the options are cloned for each file upload.
            uploadedBytes: undefined,
            // By default, failed (abort or error) file uploads are removed from the
            // global progress calculation. Set the following option to false to
            // prevent recalculating the global progress data:
            recalculateProgress: true,
            // Interval in milliseconds to calculate and trigger progress events:
            progressInterval: 100,
            // Interval in milliseconds to calculate progress bitrate:
            bitrateInterval: 500,
            // By default, uploads are started automatically when adding files:
            autoUpload: true,

            // Error and info messages:
            messages: {
                uploadedBytes: 'Uploaded bytes exceed file size'
            },

            // Translation function, gets the message key to be translated
            // and an object with context specific data as arguments:
            i18n: function (message, context) {
                message = this.messages[message] || message.toString();
                if (context) {
                    $.each(context, function (key, value) {
                        message = message.replace('{' + key + '}', value);
                    });
                }
                return message;
            },

            // Additional form data to be sent along with the file uploads can be set
            // using this option, which accepts an array of objects with name and
            // value properties, a function returning such an array, a FormData
            // object (for XHR file uploads), or a simple object.
            // The form of the first fileInput is given as parameter to the function:
            formData: function (form) {
                return form.serializeArray();
            },

            // The add callback is invoked as soon as files are added to the fileupload
            // widget (via file input selection, drag & drop, paste or add API call).
            // If the singleFileUploads option is enabled, this callback will be
            // called once for each file in the selection for XHR file uploads, else
            // once for each file selection.
            //
            // The upload starts when the submit method is invoked on the data parameter.
            // The data object contains a files property holding the added files
            // and allows you to override plugin options as well as define ajax settings.
            //
            // Listeners for this callback can also be bound the following way:
            // .bind('fileuploadadd', func);
            //
            // data.submit() returns a Promise object and allows to attach additional
            // handlers using jQuery's Deferred callbacks:
            // data.submit().done(func).fail(func).always(func);
            add: function (e, data) {
                if (e.isDefaultPrevented()) {
                    return false;
                }
                if (data.autoUpload || (data.autoUpload !== false &&
                        $(this).fileupload('option', 'autoUpload'))) {
                    data.process().done(function () {
                        data.submit();
                    });
                }
            },

            // Other callbacks:

            // Callback for the submit event of each file upload:
            // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);

            // Callback for the start of each file upload request:
            // send: function (e, data) {}, // .bind('fileuploadsend', func);

            // Callback for successful uploads:
            // done: function (e, data) {}, // .bind('fileuploaddone', func);

            // Callback for failed (abort or error) uploads:
            // fail: function (e, data) {}, // .bind('fileuploadfail', func);

            // Callback for completed (success, abort or error) requests:
            // always: function (e, data) {}, // .bind('fileuploadalways', func);

            // Callback for upload progress events:
            // progress: function (e, data) {}, // .bind('fileuploadprogress', func);

            // Callback for global upload progress events:
            // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);

            // Callback for uploads start, equivalent to the global ajaxStart event:
            // start: function (e) {}, // .bind('fileuploadstart', func);

            // Callback for uploads stop, equivalent to the global ajaxStop event:
            // stop: function (e) {}, // .bind('fileuploadstop', func);

            // Callback for change events of the fileInput(s):
            // change: function (e, data) {}, // .bind('fileuploadchange', func);

            // Callback for paste events to the pasteZone(s):
            // paste: function (e, data) {}, // .bind('fileuploadpaste', func);

            // Callback for drop events of the dropZone(s):
            // drop: function (e, data) {}, // .bind('fileuploaddrop', func);

            // Callback for dragover events of the dropZone(s):
            // dragover: function (e) {}, // .bind('fileuploaddragover', func);

            // Callback for the start of each chunk upload request:
            // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);

            // Callback for successful chunk uploads:
            // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);

            // Callback for failed (abort or error) chunk uploads:
            // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);

            // Callback for completed (success, abort or error) chunk upload requests:
            // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);

            // The plugin options are used as settings object for the ajax calls.
            // The following are jQuery ajax settings required for the file uploads:
            processData: false,
            contentType: false,
            cache: false,
            timeout: 0
        },

        // A list of options that require reinitializing event listeners and/or
        // special initialization code:
        _specialOptions: [
            'fileInput',
            'dropZone',
            'pasteZone',
            'multipart',
            'forceIframeTransport'
        ],

        _blobSlice: $.support.blobSlice && function () {
            var slice = this.slice || this.webkitSlice || this.mozSlice;
            return slice.apply(this, arguments);
        },

        _BitrateTimer: function () {
            this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
            this.loaded = 0;
            this.bitrate = 0;
            this.getBitrate = function (now, loaded, interval) {
                var timeDiff = now - this.timestamp;
                if (!this.bitrate || !interval || timeDiff > interval) {
                    this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
                    this.loaded = loaded;
                    this.timestamp = now;
                }
                return this.bitrate;
            };
        },

        _isXHRUpload: function (options) {
            return !options.forceIframeTransport &&
                ((!options.multipart && $.support.xhrFileUpload) ||
                $.support.xhrFormDataFileUpload);
        },

        _getFormData: function (options) {
            var formData;
            if ($.type(options.formData) === 'function') {
                return options.formData(options.form);
            }
            if ($.isArray(options.formData)) {
                return options.formData;
            }
            if ($.type(options.formData) === 'object') {
                formData = [];
                $.each(options.formData, function (name, value) {
                    formData.push({name: name, value: value});
                });
                return formData;
            }
            return [];
        },

        _getTotal: function (files) {
            var total = 0;
            $.each(files, function (index, file) {
                total += file.size || 1;
            });
            return total;
        },

        _initProgressObject: function (obj) {
            var progress = {
                loaded: 0,
                total: 0,
                bitrate: 0
            };
            if (obj._progress) {
                $.extend(obj._progress, progress);
            } else {
                obj._progress = progress;
            }
        },

        _initResponseObject: function (obj) {
            var prop;
            if (obj._response) {
                for (prop in obj._response) {
                    if (obj._response.hasOwnProperty(prop)) {
                        delete obj._response[prop];
                    }
                }
            } else {
                obj._response = {};
            }
        },

        _onProgress: function (e, data) {
            if (e.lengthComputable) {
                var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
                    loaded;
                if (data._time && data.progressInterval &&
                        (now - data._time < data.progressInterval) &&
                        e.loaded !== e.total) {
                    return;
                }
                data._time = now;
                loaded = Math.floor(
                    e.loaded / e.total * (data.chunkSize || data._progress.total)
                ) + (data.uploadedBytes || 0);
                // Add the difference from the previously loaded state
                // to the global loaded counter:
                this._progress.loaded += (loaded - data._progress.loaded);
                this._progress.bitrate = this._bitrateTimer.getBitrate(
                    now,
                    this._progress.loaded,
                    data.bitrateInterval
                );
                data._progress.loaded = data.loaded = loaded;
                data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
                    now,
                    loaded,
                    data.bitrateInterval
                );
                // Trigger a custom progress event with a total data property set
                // to the file size(s) of the current upload and a loaded data
                // property calculated accordingly:
                this._trigger(
                    'progress',
                    $.Event('progress', {delegatedEvent: e}),
                    data
                );
                // Trigger a global progress event for all current file uploads,
                // including ajax calls queued for sequential file uploads:
                this._trigger(
                    'progressall',
                    $.Event('progressall', {delegatedEvent: e}),
                    this._progress
                );
            }
        },

        _initProgressListener: function (options) {
            var that = this,
                xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
            // Accesss to the native XHR object is required to add event listeners
            // for the upload progress event:
            if (xhr.upload) {
                $(xhr.upload).bind('progress', function (e) {
                    var oe = e.originalEvent;
                    // Make sure the progress event properties get copied over:
                    e.lengthComputable = oe.lengthComputable;
                    e.loaded = oe.loaded;
                    e.total = oe.total;
                    that._onProgress(e, options);
                });
                options.xhr = function () {
                    return xhr;
                };
            }
        },

        _isInstanceOf: function (type, obj) {
            // Cross-frame instanceof check
            return Object.prototype.toString.call(obj) === '[object ' + type + ']';
        },

        _initXHRData: function (options) {
            var that = this,
                formData,
                file = options.files[0],
                // Ignore non-multipart setting if not supported:
                multipart = options.multipart || !$.support.xhrFileUpload,
                paramName = $.type(options.paramName) === 'array' ?
                    options.paramName[0] : options.paramName;
            options.headers = $.extend({}, options.headers);
            if (options.contentRange) {
                options.headers['Content-Range'] = options.contentRange;
            }
            if (!multipart || options.blob || !this._isInstanceOf('File', file)) {
                options.headers['Content-Disposition'] = 'attachment; filename="' +
                    encodeURI(file.uploadName || file.name) + '"';
            }
            if (!multipart) {
                options.contentType = file.type || 'application/octet-stream';
                options.data = options.blob || file;
            } else if ($.support.xhrFormDataFileUpload) {
                if (options.postMessage) {
                    // window.postMessage does not allow sending FormData
                    // objects, so we just add the File/Blob objects to
                    // the formData array and let the postMessage window
                    // create the FormData object out of this array:
                    formData = this._getFormData(options);
                    if (options.blob) {
                        formData.push({
                            name: paramName,
                            value: options.blob
                        });
                    } else {
                        $.each(options.files, function (index, file) {
                            formData.push({
                                name: ($.type(options.paramName) === 'array' &&
                                    options.paramName[index]) || paramName,
                                value: file
                            });
                        });
                    }
                } else {
                    if (that._isInstanceOf('FormData', options.formData)) {
                        formData = options.formData;
                    } else {
                        formData = new FormData();
                        $.each(this._getFormData(options), function (index, field) {
                            formData.append(field.name, field.value);
                        });
                    }
                    if (options.blob) {
                        formData.append(
                            paramName,
                            options.blob,
                            file.uploadName || file.name
                        );
                    } else {
                        $.each(options.files, function (index, file) {
                            // This check allows the tests to run with
                            // dummy objects:
                            if (that._isInstanceOf('File', file) ||
                                    that._isInstanceOf('Blob', file)) {
                                formData.append(
                                    ($.type(options.paramName) === 'array' &&
                                        options.paramName[index]) || paramName,
                                    file,
                                    file.uploadName || file.name
                                );
                            }
                        });
                    }
                }
                options.data = formData;
            }
            // Blob reference is not needed anymore, free memory:
            options.blob = null;
        },

        _initIframeSettings: function (options) {
            var targetHost = $('<a></a>').prop('href', options.url).prop('host');
            // Setting the dataType to iframe enables the iframe transport:
            options.dataType = 'iframe ' + (options.dataType || '');
            // The iframe transport accepts a serialized array as form data:
            options.formData = this._getFormData(options);
            // Add redirect url to form data on cross-domain uploads:
            if (options.redirect && targetHost && targetHost !== location.host) {
                options.formData.push({
                    name: options.redirectParamName || 'redirect',
                    value: options.redirect
                });
            }
        },

        _initDataSettings: function (options) {
            if (this._isXHRUpload(options)) {
                if (!this._chunkedUpload(options, true)) {
                    if (!options.data) {
                        this._initXHRData(options);
                    }
                    this._initProgressListener(options);
                }
                if (options.postMessage) {
                    // Setting the dataType to postmessage enables the
                    // postMessage transport:
                    options.dataType = 'postmessage ' + (options.dataType || '');
                }
            } else {
                this._initIframeSettings(options);
            }
        },

        _getParamName: function (options) {
            var fileInput = $(options.fileInput),
                paramName = options.paramName;
            if (!paramName) {
                paramName = [];
                fileInput.each(function () {
                    var input = $(this),
                        name = input.prop('name') || 'files[]',
                        i = (input.prop('files') || [1]).length;
                    while (i) {
                        paramName.push(name);
                        i -= 1;
                    }
                });
                if (!paramName.length) {
                    paramName = [fileInput.prop('name') || 'files[]'];
                }
            } else if (!$.isArray(paramName)) {
                paramName = [paramName];
            }
            return paramName;
        },

        _initFormSettings: function (options) {
            // Retrieve missing options from the input field and the
            // associated form, if available:
            if (!options.form || !options.form.length) {
                options.form = $(options.fileInput.prop('form'));
                // If the given file input doesn't have an associated form,
                // use the default widget file input's form:
                if (!options.form.length) {
                    options.form = $(this.options.fileInput.prop('form'));
                }
            }
            options.paramName = this._getParamName(options);
            if (!options.url) {
                options.url = options.form.prop('action') || location.href;
            }
            // The HTTP request method must be "POST" or "PUT":
            options.type = (options.type ||
                ($.type(options.form.prop('method')) === 'string' &&
                    options.form.prop('method')) || ''
                ).toUpperCase();
            if (options.type !== 'POST' && options.type !== 'PUT' &&
                    options.type !== 'PATCH') {
                options.type = 'POST';
            }
            if (!options.formAcceptCharset) {
                options.formAcceptCharset = options.form.attr('accept-charset');
            }
        },

        _getAJAXSettings: function (data) {
            var options = $.extend({}, this.options, data);
            this._initFormSettings(options);
            this._initDataSettings(options);
            return options;
        },

        // jQuery 1.6 doesn't provide .state(),
        // while jQuery 1.8+ removed .isRejected() and .isResolved():
        _getDeferredState: function (deferred) {
            if (deferred.state) {
                return deferred.state();
            }
            if (deferred.isResolved()) {
                return 'resolved';
            }
            if (deferred.isRejected()) {
                return 'rejected';
            }
            return 'pending';
        },

        // Maps jqXHR callbacks to the equivalent
        // methods of the given Promise object:
        _enhancePromise: function (promise) {
            promise.success = promise.done;
            promise.error = promise.fail;
            promise.complete = promise.always;
            return promise;
        },

        // Creates and returns a Promise object enhanced with
        // the jqXHR methods abort, success, error and complete:
        _getXHRPromise: function (resolveOrReject, context, args) {
            var dfd = $.Deferred(),
                promise = dfd.promise();
            context = context || this.options.context || promise;
            if (resolveOrReject === true) {
                dfd.resolveWith(context, args);
            } else if (resolveOrReject === false) {
                dfd.rejectWith(context, args);
            }
            promise.abort = dfd.promise;
            return this._enhancePromise(promise);
        },

        // Adds convenience methods to the data callback argument:
        _addConvenienceMethods: function (e, data) {
            var that = this,
                getPromise = function (args) {
                    return $.Deferred().resolveWith(that, args).promise();
                };
            data.process = function (resolveFunc, rejectFunc) {
                if (resolveFunc || rejectFunc) {
                    data._processQueue = this._processQueue =
                        (this._processQueue || getPromise([this])).then(
                            function () {
                                if (data.errorThrown) {
                                    return $.Deferred()
                                        .rejectWith(that, [data]).promise();
                                }
                                return getPromise(arguments);
                            }
                        ).then(resolveFunc, rejectFunc);
                }
                return this._processQueue || getPromise([this]);
            };
            data.submit = function () {
                if (this.state() !== 'pending') {
                    data.jqXHR = this.jqXHR =
                        (that._trigger(
                            'submit',
                            $.Event('submit', {delegatedEvent: e}),
                            this
                        ) !== false) && that._onSend(e, this);
                }
                return this.jqXHR || that._getXHRPromise();
            };
            data.abort = function () {
                if (this.jqXHR) {
                    return this.jqXHR.abort();
                }
                this.errorThrown = 'abort';
                that._trigger('fail', null, this);
                return that._getXHRPromise(false);
            };
            data.state = function () {
                if (this.jqXHR) {
                    return that._getDeferredState(this.jqXHR);
                }
                if (this._processQueue) {
                    return that._getDeferredState(this._processQueue);
                }
            };
            data.processing = function () {
                return !this.jqXHR && this._processQueue && that
                    ._getDeferredState(this._processQueue) === 'pending';
            };
            data.progress = function () {
                return this._progress;
            };
            data.response = function () {
                return this._response;
            };
        },

        // Parses the Range header from the server response
        // and returns the uploaded bytes:
        _getUploadedBytes: function (jqXHR) {
            var range = jqXHR.getResponseHeader('Range'),
                parts = range && range.split('-'),
                upperBytesPos = parts && parts.length > 1 &&
                    parseInt(parts[1], 10);
            return upperBytesPos && upperBytesPos + 1;
        },

        // Uploads a file in multiple, sequential requests
        // by splitting the file up in multiple blob chunks.
        // If the second parameter is true, only tests if the file
        // should be uploaded in chunks, but does not invoke any
        // upload requests:
        _chunkedUpload: function (options, testOnly) {
            options.uploadedBytes = options.uploadedBytes || 0;
            var that = this,
                file = options.files[0],
                fs = file.size,
                ub = options.uploadedBytes,
                mcs = options.maxChunkSize || fs,
                slice = this._blobSlice,
                dfd = $.Deferred(),
                promise = dfd.promise(),
                jqXHR,
                upload;
            if (!(this._isXHRUpload(options) && slice && (ub || ($.type(mcs) === 'function' ? mcs(options) : mcs) < fs)) ||
                    options.data) {
                return false;
            }
            if (testOnly) {
                return true;
            }
            if (ub >= fs) {
                file.error = options.i18n('uploadedBytes');
                return this._getXHRPromise(
                    false,
                    options.context,
                    [null, 'error', file.error]
                );
            }
            // The chunk upload method:
            upload = function () {
                // Clone the options object for each chunk upload:
                var o = $.extend({}, options),
                    currentLoaded = o._progress.loaded;
                o.blob = slice.call(
                    file,
                    ub,
                    ub + ($.type(mcs) === 'function' ? mcs(o) : mcs),
                    file.type
                );
                // Store the current chunk size, as the blob itself
                // will be dereferenced after data processing:
                o.chunkSize = o.blob.size;
                // Expose the chunk bytes position range:
                o.contentRange = 'bytes ' + ub + '-' +
                    (ub + o.chunkSize - 1) + '/' + fs;
                // Process the upload data (the blob and potential form data):
                that._initXHRData(o);
                // Add progress listeners for this chunk upload:
                that._initProgressListener(o);
                jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
                        that._getXHRPromise(false, o.context))
                    .done(function (result, textStatus, jqXHR) {
                        ub = that._getUploadedBytes(jqXHR) ||
                            (ub + o.chunkSize);
                        // Create a progress event if no final progress event
                        // with loaded equaling total has been triggered
                        // for this chunk:
                        if (currentLoaded + o.chunkSize - o._progress.loaded) {
                            that._onProgress($.Event('progress', {
                                lengthComputable: true,
                                loaded: ub - o.uploadedBytes,
                                total: ub - o.uploadedBytes
                            }), o);
                        }
                        options.uploadedBytes = o.uploadedBytes = ub;
                        o.result = result;
                        o.textStatus = textStatus;
                        o.jqXHR = jqXHR;
                        that._trigger('chunkdone', null, o);
                        that._trigger('chunkalways', null, o);
                        if (ub < fs) {
                            // File upload not yet complete,
                            // continue with the next chunk:
                            upload();
                        } else {
                            dfd.resolveWith(
                                o.context,
                                [result, textStatus, jqXHR]
                            );
                        }
                    })
                    .fail(function (jqXHR, textStatus, errorThrown) {
                        o.jqXHR = jqXHR;
                        o.textStatus = textStatus;
                        o.errorThrown = errorThrown;
                        that._trigger('chunkfail', null, o);
                        that._trigger('chunkalways', null, o);
                        dfd.rejectWith(
                            o.context,
                            [jqXHR, textStatus, errorThrown]
                        );
                    });
            };
            this._enhancePromise(promise);
            promise.abort = function () {
                return jqXHR.abort();
            };
            upload();
            return promise;
        },

        _beforeSend: function (e, data) {
            if (this._active === 0) {
                // the start callback is triggered when an upload starts
                // and no other uploads are currently running,
                // equivalent to the global ajaxStart event:
                this._trigger('start');
                // Set timer for global bitrate progress calculation:
                this._bitrateTimer = new this._BitrateTimer();
                // Reset the global progress values:
                this._progress.loaded = this._progress.total = 0;
                this._progress.bitrate = 0;
            }
            // Make sure the container objects for the .response() and
            // .progress() methods on the data object are available
            // and reset to their initial state:
            this._initResponseObject(data);
            this._initProgressObject(data);
            data._progress.loaded = data.loaded = data.uploadedBytes || 0;
            data._progress.total = data.total = this._getTotal(data.files) || 1;
            data._progress.bitrate = data.bitrate = 0;
            this._active += 1;
            // Initialize the global progress values:
            this._progress.loaded += data.loaded;
            this._progress.total += data.total;
        },

        _onDone: function (result, textStatus, jqXHR, options) {
            var total = options._progress.total,
                response = options._response;
            if (options._progress.loaded < total) {
                // Create a progress event if no final progress event
                // with loaded equaling total has been triggered:
                this._onProgress($.Event('progress', {
                    lengthComputable: true,
                    loaded: total,
                    total: total
                }), options);
            }
            response.result = options.result = result;
            response.textStatus = options.textStatus = textStatus;
            response.jqXHR = options.jqXHR = jqXHR;
            this._trigger('done', null, options);
        },

        _onFail: function (jqXHR, textStatus, errorThrown, options) {
            var response = options._response;
            if (options.recalculateProgress) {
                // Remove the failed (error or abort) file upload from
                // the global progress calculation:
                this._progress.loaded -= options._progress.loaded;
                this._progress.total -= options._progress.total;
            }
            response.jqXHR = options.jqXHR = jqXHR;
            response.textStatus = options.textStatus = textStatus;
            response.errorThrown = options.errorThrown = errorThrown;
            this._trigger('fail', null, options);
        },

        _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
            // jqXHRorResult, textStatus and jqXHRorError are added to the
            // options object via done and fail callbacks
            this._trigger('always', null, options);
        },

        _onSend: function (e, data) {
            if (!data.submit) {
                this._addConvenienceMethods(e, data);
            }
            var that = this,
                jqXHR,
                aborted,
                slot,
                pipe,
                options = that._getAJAXSettings(data),
                send = function () {
                    that._sending += 1;
                    // Set timer for bitrate progress calculation:
                    options._bitrateTimer = new that._BitrateTimer();
                    jqXHR = jqXHR || (
                        ((aborted || that._trigger(
                            'send',
                            $.Event('send', {delegatedEvent: e}),
                            options
                        ) === false) &&
                        that._getXHRPromise(false, options.context, aborted)) ||
                        that._chunkedUpload(options) || $.ajax(options)
                    ).done(function (result, textStatus, jqXHR) {
                        that._onDone(result, textStatus, jqXHR, options);
                    }).fail(function (jqXHR, textStatus, errorThrown) {
                        that._onFail(jqXHR, textStatus, errorThrown, options);
                    }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
                        that._onAlways(
                            jqXHRorResult,
                            textStatus,
                            jqXHRorError,
                            options
                        );
                        that._sending -= 1;
                        that._active -= 1;
                        if (options.limitConcurrentUploads &&
                                options.limitConcurrentUploads > that._sending) {
                            // Start the next queued upload,
                            // that has not been aborted:
                            var nextSlot = that._slots.shift();
                            while (nextSlot) {
                                if (that._getDeferredState(nextSlot) === 'pending') {
                                    nextSlot.resolve();
                                    break;
                                }
                                nextSlot = that._slots.shift();
                            }
                        }
                        if (that._active === 0) {
                            // The stop callback is triggered when all uploads have
                            // been completed, equivalent to the global ajaxStop event:
                            that._trigger('stop');
                        }
                    });
                    return jqXHR;
                };
            this._beforeSend(e, options);
            if (this.options.sequentialUploads ||
                    (this.options.limitConcurrentUploads &&
                    this.options.limitConcurrentUploads <= this._sending)) {
                if (this.options.limitConcurrentUploads > 1) {
                    slot = $.Deferred();
                    this._slots.push(slot);
                    pipe = slot.then(send);
                } else {
                    this._sequence = this._sequence.then(send, send);
                    pipe = this._sequence;
                }
                // Return the piped Promise object, enhanced with an abort method,
                // which is delegated to the jqXHR object of the current upload,
                // and jqXHR callbacks mapped to the equivalent Promise methods:
                pipe.abort = function () {
                    aborted = [undefined, 'abort', 'abort'];
                    if (!jqXHR) {
                        if (slot) {
                            slot.rejectWith(options.context, aborted);
                        }
                        return send();
                    }
                    return jqXHR.abort();
                };
                return this._enhancePromise(pipe);
            }
            return send();
        },

        _onAdd: function (e, data) {
            var that = this,
                result = true,
                options = $.extend({}, this.options, data),
                files = data.files,
                filesLength = files.length,
                limit = options.limitMultiFileUploads,
                limitSize = options.limitMultiFileUploadSize,
                overhead = options.limitMultiFileUploadSizeOverhead,
                batchSize = 0,
                paramName = this._getParamName(options),
                paramNameSet,
                paramNameSlice,
                fileSet,
                i,
                j = 0;
            if (!filesLength) {
                return false;
            }
            if (limitSize && files[0].size === undefined) {
                limitSize = undefined;
            }
            if (!(options.singleFileUploads || limit || limitSize) ||
                    !this._isXHRUpload(options)) {
                fileSet = [files];
                paramNameSet = [paramName];
            } else if (!(options.singleFileUploads || limitSize) && limit) {
                fileSet = [];
                paramNameSet = [];
                for (i = 0; i < filesLength; i += limit) {
                    fileSet.push(files.slice(i, i + limit));
                    paramNameSlice = paramName.slice(i, i + limit);
                    if (!paramNameSlice.length) {
                        paramNameSlice = paramName;
                    }
                    paramNameSet.push(paramNameSlice);
                }
            } else if (!options.singleFileUploads && limitSize) {
                fileSet = [];
                paramNameSet = [];
                for (i = 0; i < filesLength; i = i + 1) {
                    batchSize += files[i].size + overhead;
                    if (i + 1 === filesLength ||
                            ((batchSize + files[i + 1].size + overhead) > limitSize) ||
                            (limit && i + 1 - j >= limit)) {
                        fileSet.push(files.slice(j, i + 1));
                        paramNameSlice = paramName.slice(j, i + 1);
                        if (!paramNameSlice.length) {
                            paramNameSlice = paramName;
                        }
                        paramNameSet.push(paramNameSlice);
                        j = i + 1;
                        batchSize = 0;
                    }
                }
            } else {
                paramNameSet = paramName;
            }
            data.originalFiles = files;
            $.each(fileSet || files, function (index, element) {
                var newData = $.extend({}, data);
                newData.files = fileSet ? element : [element];
                newData.paramName = paramNameSet[index];
                that._initResponseObject(newData);
                that._initProgressObject(newData);
                that._addConvenienceMethods(e, newData);
                result = that._trigger(
                    'add',
                    $.Event('add', {delegatedEvent: e}),
                    newData
                );
                return result;
            });
            return result;
        },

        _replaceFileInput: function (data) {
            var input = data.fileInput,
                inputClone = input.clone(true),
                restoreFocus = input.is(document.activeElement);
            // Add a reference for the new cloned file input to the data argument:
            data.fileInputClone = inputClone;
            $('<form></form>').append(inputClone)[0].reset();
            // Detaching allows to insert the fileInput on another form
            // without loosing the file input value:
            input.after(inputClone).detach();
            // If the fileInput had focus before it was detached,
            // restore focus to the inputClone.
            if (restoreFocus) {
                inputClone.focus();
            }
            // Avoid memory leaks with the detached file input:
            $.cleanData(input.unbind('remove'));
            // Replace the original file input element in the fileInput
            // elements set with the clone, which has been copied including
            // event handlers:
            this.options.fileInput = this.options.fileInput.map(function (i, el) {
                if (el === input[0]) {
                    return inputClone[0];
                }
                return el;
            });
            // If the widget has been initialized on the file input itself,
            // override this.element with the file input clone:
            if (input[0] === this.element[0]) {
                this.element = inputClone;
            }
        },

        _handleFileTreeEntry: function (entry, path) {
            var that = this,
                dfd = $.Deferred(),
                entries = [],
                dirReader,
                errorHandler = function (e) {
                    if (e && !e.entry) {
                        e.entry = entry;
                    }
                    // Since $.when returns immediately if one
                    // Deferred is rejected, we use resolve instead.
                    // This allows valid files and invalid items
                    // to be returned together in one set:
                    dfd.resolve([e]);
                },
                successHandler = function (entries) {
                    that._handleFileTreeEntries(
                        entries,
                        path + entry.name + '/'
                    ).done(function (files) {
                        dfd.resolve(files);
                    }).fail(errorHandler);
                },
                readEntries = function () {
                    dirReader.readEntries(function (results) {
                        if (!results.length) {
                            successHandler(entries);
                        } else {
                            entries = entries.concat(results);
                            readEntries();
                        }
                    }, errorHandler);
                };
            path = path || '';
            if (entry.isFile) {
                if (entry._file) {
                    // Workaround for Chrome bug #149735
                    entry._file.relativePath = path;
                    dfd.resolve(entry._file);
                } else {
                    entry.file(function (file) {
                        file.relativePath = path;
                        dfd.resolve(file);
                    }, errorHandler);
                }
            } else if (entry.isDirectory) {
                dirReader = entry.createReader();
                readEntries();
            } else {
                // Return an empy list for file system items
                // other than files or directories:
                dfd.resolve([]);
            }
            return dfd.promise();
        },

        _handleFileTreeEntries: function (entries, path) {
            var that = this;
            return $.when.apply(
                $,
                $.map(entries, function (entry) {
                    return that._handleFileTreeEntry(entry, path);
                })
            ).then(function () {
                return Array.prototype.concat.apply(
                    [],
                    arguments
                );
            });
        },

        _getDroppedFiles: function (dataTransfer) {
            dataTransfer = dataTransfer || {};
            var items = dataTransfer.items;
            if (items && items.length && (items[0].webkitGetAsEntry ||
                    items[0].getAsEntry)) {
                return this._handleFileTreeEntries(
                    $.map(items, function (item) {
                        var entry;
                        if (item.webkitGetAsEntry) {
                            entry = item.webkitGetAsEntry();
                            if (entry) {
                                // Workaround for Chrome bug #149735:
                                entry._file = item.getAsFile();
                            }
                            return entry;
                        }
                        return item.getAsEntry();
                    })
                );
            }
            return $.Deferred().resolve(
                $.makeArray(dataTransfer.files)
            ).promise();
        },

        _getSingleFileInputFiles: function (fileInput) {
            fileInput = $(fileInput);
            var entries = fileInput.prop('webkitEntries') ||
                    fileInput.prop('entries'),
                files,
                value;
            if (entries && entries.length) {
                return this._handleFileTreeEntries(entries);
            }
            files = $.makeArray(fileInput.prop('files'));
            if (!files.length) {
                value = fileInput.prop('value');
                if (!value) {
                    return $.Deferred().resolve([]).promise();
                }
                // If the files property is not available, the browser does not
                // support the File API and we add a pseudo File object with
                // the input value as name with path information removed:
                files = [{name: value.replace(/^.*\\/, '')}];
            } else if (files[0].name === undefined && files[0].fileName) {
                // File normalization for Safari 4 and Firefox 3:
                $.each(files, function (index, file) {
                    file.name = file.fileName;
                    file.size = file.fileSize;
                });
            }
            return $.Deferred().resolve(files).promise();
        },

        _getFileInputFiles: function (fileInput) {
            if (!(fileInput instanceof $) || fileInput.length === 1) {
                return this._getSingleFileInputFiles(fileInput);
            }
            return $.when.apply(
                $,
                $.map(fileInput, this._getSingleFileInputFiles)
            ).then(function () {
                return Array.prototype.concat.apply(
                    [],
                    arguments
                );
            });
        },

        _onChange: function (e) {
            var that = this,
                data = {
                    fileInput: $(e.target),
                    form: $(e.target.form)
                };
            this._getFileInputFiles(data.fileInput).always(function (files) {
                data.files = files;
                if (that.options.replaceFileInput) {
                    that._replaceFileInput(data);
                }
                if (that._trigger(
                        'change',
                        $.Event('change', {delegatedEvent: e}),
                        data
                    ) !== false) {
                    that._onAdd(e, data);
                }
            });
        },

        _onPaste: function (e) {
            var items = e.originalEvent && e.originalEvent.clipboardData &&
                    e.originalEvent.clipboardData.items,
                data = {files: []};
            if (items && items.length) {
                $.each(items, function (index, item) {
                    var file = item.getAsFile && item.getAsFile();
                    if (file) {
                        data.files.push(file);
                    }
                });
                if (this._trigger(
                        'paste',
                        $.Event('paste', {delegatedEvent: e}),
                        data
                    ) !== false) {
                    this._onAdd(e, data);
                }
            }
        },

        _onDrop: function (e) {
            e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
            var that = this,
                dataTransfer = e.dataTransfer,
                data = {};
            if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
                e.preventDefault();
                this._getDroppedFiles(dataTransfer).always(function (files) {
                    data.files = files;
                    if (that._trigger(
                            'drop',
                            $.Event('drop', {delegatedEvent: e}),
                            data
                        ) !== false) {
                        that._onAdd(e, data);
                    }
                });
            }
        },

        _onDragOver: getDragHandler('dragover'),

        _onDragEnter: getDragHandler('dragenter'),

        _onDragLeave: getDragHandler('dragleave'),

        _initEventHandlers: function () {
            if (this._isXHRUpload(this.options)) {
                this._on(this.options.dropZone, {
                    dragover: this._onDragOver,
                    drop: this._onDrop,
                    // event.preventDefault() on dragenter is required for IE10+:
                    dragenter: this._onDragEnter,
                    // dragleave is not required, but added for completeness:
                    dragleave: this._onDragLeave
                });
                this._on(this.options.pasteZone, {
                    paste: this._onPaste
                });
            }
            if ($.support.fileInput) {
                this._on(this.options.fileInput, {
                    change: this._onChange
                });
            }
        },

        _destroyEventHandlers: function () {
            this._off(this.options.dropZone, 'dragenter dragleave dragover drop');
            this._off(this.options.pasteZone, 'paste');
            this._off(this.options.fileInput, 'change');
        },

        _destroy: function () {
            this._destroyEventHandlers();
        },

        _setOption: function (key, value) {
            var reinit = $.inArray(key, this._specialOptions) !== -1;
            if (reinit) {
                this._destroyEventHandlers();
            }
            this._super(key, value);
            if (reinit) {
                this._initSpecialOptions();
                this._initEventHandlers();
            }
        },

        _initSpecialOptions: function () {
            var options = this.options;
            if (options.fileInput === undefined) {
                options.fileInput = this.element.is('input[type="file"]') ?
                        this.element : this.element.find('input[type="file"]');
            } else if (!(options.fileInput instanceof $)) {
                options.fileInput = $(options.fileInput);
            }
            if (!(options.dropZone instanceof $)) {
                options.dropZone = $(options.dropZone);
            }
            if (!(options.pasteZone instanceof $)) {
                options.pasteZone = $(options.pasteZone);
            }
        },

        _getRegExp: function (str) {
            var parts = str.split('/'),
                modifiers = parts.pop();
            parts.shift();
            return new RegExp(parts.join('/'), modifiers);
        },

        _isRegExpOption: function (key, value) {
            return key !== 'url' && $.type(value) === 'string' &&
                /^\/.*\/[igm]{0,3}$/.test(value);
        },

        _initDataAttributes: function () {
            var that = this,
                options = this.options,
                data = this.element.data();
            // Initialize options set via HTML5 data-attributes:
            $.each(
                this.element[0].attributes,
                function (index, attr) {
                    var key = attr.name.toLowerCase(),
                        value;
                    if (/^data-/.test(key)) {
                        // Convert hyphen-ated key to camelCase:
                        key = key.slice(5).replace(/-[a-z]/g, function (str) {
                            return str.charAt(1).toUpperCase();
                        });
                        value = data[key];
                        if (that._isRegExpOption(key, value)) {
                            value = that._getRegExp(value);
                        }
                        options[key] = value;
                    }
                }
            );
        },

        _create: function () {
            this._initDataAttributes();
            this._initSpecialOptions();
            this._slots = [];
            this._sequence = this._getXHRPromise(true);
            this._sending = this._active = 0;
            this._initProgressObject(this);
            this._initEventHandlers();
        },

        // This method is exposed to the widget API and allows to query
        // the number of active uploads:
        active: function () {
            return this._active;
        },

        // This method is exposed to the widget API and allows to query
        // the widget upload progress.
        // It returns an object with loaded, total and bitrate properties
        // for the running uploads:
        progress: function () {
            return this._progress;
        },

        // This method is exposed to the widget API and allows adding files
        // using the fileupload API. The data parameter accepts an object which
        // must have a files property and can contain additional options:
        // .fileupload('add', {files: filesList});
        add: function (data) {
            var that = this;
            if (!data || this.options.disabled) {
                return;
            }
            if (data.fileInput && !data.files) {
                this._getFileInputFiles(data.fileInput).always(function (files) {
                    data.files = files;
                    that._onAdd(null, data);
                });
            } else {
                data.files = $.makeArray(data.files);
                this._onAdd(null, data);
            }
        },

        // This method is exposed to the widget API and allows sending files
        // using the fileupload API. The data parameter accepts an object which
        // must have a files or fileInput property and can contain additional options:
        // .fileupload('send', {files: filesList});
        // The method returns a Promise object for the file upload call.
        send: function (data) {
            if (data && !this.options.disabled) {
                if (data.fileInput && !data.files) {
                    var that = this,
                        dfd = $.Deferred(),
                        promise = dfd.promise(),
                        jqXHR,
                        aborted;
                    promise.abort = function () {
                        aborted = true;
                        if (jqXHR) {
                            return jqXHR.abort();
                        }
                        dfd.reject(null, 'abort', 'abort');
                        return promise;
                    };
                    this._getFileInputFiles(data.fileInput).always(
                        function (files) {
                            if (aborted) {
                                return;
                            }
                            if (!files.length) {
                                dfd.reject();
                                return;
                            }
                            data.files = files;
                            jqXHR = that._onSend(null, data);
                            jqXHR.then(
                                function (result, textStatus, jqXHR) {
                                    dfd.resolve(result, textStatus, jqXHR);
                                },
                                function (jqXHR, textStatus, errorThrown) {
                                    dfd.reject(jqXHR, textStatus, errorThrown);
                                }
                            );
                        }
                    );
                    return this._enhancePromise(promise);
                }
                data.files = $.makeArray(data.files);
                if (data.files.length) {
                    return this._onSend(null, data);
                }
            }
            return this._getXHRPromise(false, data && data.context);
        }

    });

}));
;
/* pako 1.0.6 nodeca/pako */(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.pako = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
'use strict';


var TYPED_OK =  (typeof Uint8Array !== 'undefined') &&
                (typeof Uint16Array !== 'undefined') &&
                (typeof Int32Array !== 'undefined');

function _has(obj, key) {
  return Object.prototype.hasOwnProperty.call(obj, key);
}

exports.assign = function (obj /*from1, from2, from3, ...*/) {
  var sources = Array.prototype.slice.call(arguments, 1);
  while (sources.length) {
    var source = sources.shift();
    if (!source) { continue; }

    if (typeof source !== 'object') {
      throw new TypeError(source + 'must be non-object');
    }

    for (var p in source) {
      if (_has(source, p)) {
        obj[p] = source[p];
      }
    }
  }

  return obj;
};


// reduce buffer size, avoiding mem copy
exports.shrinkBuf = function (buf, size) {
  if (buf.length === size) { return buf; }
  if (buf.subarray) { return buf.subarray(0, size); }
  buf.length = size;
  return buf;
};


var fnTyped = {
  arraySet: function (dest, src, src_offs, len, dest_offs) {
    if (src.subarray && dest.subarray) {
      dest.set(src.subarray(src_offs, src_offs + len), dest_offs);
      return;
    }
    // Fallback to ordinary array
    for (var i = 0; i < len; i++) {
      dest[dest_offs + i] = src[src_offs + i];
    }
  },
  // Join array of chunks to single array.
  flattenChunks: function (chunks) {
    var i, l, len, pos, chunk, result;

    // calculate data length
    len = 0;
    for (i = 0, l = chunks.length; i < l; i++) {
      len += chunks[i].length;
    }

    // join chunks
    result = new Uint8Array(len);
    pos = 0;
    for (i = 0, l = chunks.length; i < l; i++) {
      chunk = chunks[i];
      result.set(chunk, pos);
      pos += chunk.length;
    }

    return result;
  }
};

var fnUntyped = {
  arraySet: function (dest, src, src_offs, len, dest_offs) {
    for (var i = 0; i < len; i++) {
      dest[dest_offs + i] = src[src_offs + i];
    }
  },
  // Join array of chunks to single array.
  flattenChunks: function (chunks) {
    return [].concat.apply([], chunks);
  }
};


// Enable/Disable typed arrays use, for testing
//
exports.setTyped = function (on) {
  if (on) {
    exports.Buf8  = Uint8Array;
    exports.Buf16 = Uint16Array;
    exports.Buf32 = Int32Array;
    exports.assign(exports, fnTyped);
  } else {
    exports.Buf8  = Array;
    exports.Buf16 = Array;
    exports.Buf32 = Array;
    exports.assign(exports, fnUntyped);
  }
};

exports.setTyped(TYPED_OK);

},{}],2:[function(require,module,exports){
// String encode/decode helpers
'use strict';


var utils = require('./common');


// Quick check if we can use fast array to bin string conversion
//
// - apply(Array) can fail on Android 2.2
// - apply(Uint8Array) can fail on iOS 5.1 Safari
//
var STR_APPLY_OK = true;
var STR_APPLY_UIA_OK = true;

try { String.fromCharCode.apply(null, [ 0 ]); } catch (__) { STR_APPLY_OK = false; }
try { String.fromCharCode.apply(null, new Uint8Array(1)); } catch (__) { STR_APPLY_UIA_OK = false; }


// Table with utf8 lengths (calculated by first byte of sequence)
// Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS,
// because max possible codepoint is 0x10ffff
var _utf8len = new utils.Buf8(256);
for (var q = 0; q < 256; q++) {
  _utf8len[q] = (q >= 252 ? 6 : q >= 248 ? 5 : q >= 240 ? 4 : q >= 224 ? 3 : q >= 192 ? 2 : 1);
}
_utf8len[254] = _utf8len[254] = 1; // Invalid sequence start


// convert string to array (typed, when possible)
exports.string2buf = function (str) {
  var buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0;

  // count binary size
  for (m_pos = 0; m_pos < str_len; m_pos++) {
    c = str.charCodeAt(m_pos);
    if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) {
      c2 = str.charCodeAt(m_pos + 1);
      if ((c2 & 0xfc00) === 0xdc00) {
        c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00);
        m_pos++;
      }
    }
    buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4;
  }

  // allocate buffer
  buf = new utils.Buf8(buf_len);

  // convert
  for (i = 0, m_pos = 0; i < buf_len; m_pos++) {
    c = str.charCodeAt(m_pos);
    if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) {
      c2 = str.charCodeAt(m_pos + 1);
      if ((c2 & 0xfc00) === 0xdc00) {
        c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00);
        m_pos++;
      }
    }
    if (c < 0x80) {
      /* one byte */
      buf[i++] = c;
    } else if (c < 0x800) {
      /* two bytes */
      buf[i++] = 0xC0 | (c >>> 6);
      buf[i++] = 0x80 | (c & 0x3f);
    } else if (c < 0x10000) {
      /* three bytes */
      buf[i++] = 0xE0 | (c >>> 12);
      buf[i++] = 0x80 | (c >>> 6 & 0x3f);
      buf[i++] = 0x80 | (c & 0x3f);
    } else {
      /* four bytes */
      buf[i++] = 0xf0 | (c >>> 18);
      buf[i++] = 0x80 | (c >>> 12 & 0x3f);
      buf[i++] = 0x80 | (c >>> 6 & 0x3f);
      buf[i++] = 0x80 | (c & 0x3f);
    }
  }

  return buf;
};

// Helper (used in 2 places)
function buf2binstring(buf, len) {
  // use fallback for big arrays to avoid stack overflow
  if (len < 65537) {
    if ((buf.subarray && STR_APPLY_UIA_OK) || (!buf.subarray && STR_APPLY_OK)) {
      return String.fromCharCode.apply(null, utils.shrinkBuf(buf, len));
    }
  }

  var result = '';
  for (var i = 0; i < len; i++) {
    result += String.fromCharCode(buf[i]);
  }
  return result;
}


// Convert byte array to binary string
exports.buf2binstring = function (buf) {
  return buf2binstring(buf, buf.length);
};


// Convert binary string (typed, when possible)
exports.binstring2buf = function (str) {
  var buf = new utils.Buf8(str.length);
  for (var i = 0, len = buf.length; i < len; i++) {
    buf[i] = str.charCodeAt(i);
  }
  return buf;
};


// convert array to string
exports.buf2string = function (buf, max) {
  var i, out, c, c_len;
  var len = max || buf.length;

  // Reserve max possible length (2 words per char)
  // NB: by unknown reasons, Array is significantly faster for
  //     String.fromCharCode.apply than Uint16Array.
  var utf16buf = new Array(len * 2);

  for (out = 0, i = 0; i < len;) {
    c = buf[i++];
    // quick process ascii
    if (c < 0x80) { utf16buf[out++] = c; continue; }

    c_len = _utf8len[c];
    // skip 5 & 6 byte codes
    if (c_len > 4) { utf16buf[out++] = 0xfffd; i += c_len - 1; continue; }

    // apply mask on first byte
    c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07;
    // join the rest
    while (c_len > 1 && i < len) {
      c = (c << 6) | (buf[i++] & 0x3f);
      c_len--;
    }

    // terminated by end of string?
    if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; }

    if (c < 0x10000) {
      utf16buf[out++] = c;
    } else {
      c -= 0x10000;
      utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff);
      utf16buf[out++] = 0xdc00 | (c & 0x3ff);
    }
  }

  return buf2binstring(utf16buf, out);
};


// Calculate max possible position in utf8 buffer,
// that will not break sequence. If that's not possible
// - (very small limits) return max size as is.
//
// buf[] - utf8 bytes array
// max   - length limit (mandatory);
exports.utf8border = function (buf, max) {
  var pos;

  max = max || buf.length;
  if (max > buf.length) { max = buf.length; }

  // go back from last position, until start of sequence found
  pos = max - 1;
  while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; }

  // Very small and broken sequence,
  // return max, because we should return something anyway.
  if (pos < 0) { return max; }

  // If we came to start of buffer - that means buffer is too small,
  // return max too.
  if (pos === 0) { return max; }

  return (pos + _utf8len[buf[pos]] > max) ? pos : max;
};

},{"./common":1}],3:[function(require,module,exports){
'use strict';

// Note: adler32 takes 12% for level 0 and 2% for level 6.
// It isn't worth it to make additional optimizations as in original.
// Small size is preferable.

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

function adler32(adler, buf, len, pos) {
  var s1 = (adler & 0xffff) |0,
      s2 = ((adler >>> 16) & 0xffff) |0,
      n = 0;

  while (len !== 0) {
    // Set limit ~ twice less than 5552, to keep
    // s2 in 31-bits, because we force signed ints.
    // in other case %= will fail.
    n = len > 2000 ? 2000 : len;
    len -= n;

    do {
      s1 = (s1 + buf[pos++]) |0;
      s2 = (s2 + s1) |0;
    } while (--n);

    s1 %= 65521;
    s2 %= 65521;
  }

  return (s1 | (s2 << 16)) |0;
}


module.exports = adler32;

},{}],4:[function(require,module,exports){
'use strict';

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

module.exports = {

  /* Allowed flush values; see deflate() and inflate() below for details */
  Z_NO_FLUSH:         0,
  Z_PARTIAL_FLUSH:    1,
  Z_SYNC_FLUSH:       2,
  Z_FULL_FLUSH:       3,
  Z_FINISH:           4,
  Z_BLOCK:            5,
  Z_TREES:            6,

  /* Return codes for the compression/decompression functions. Negative values
  * are errors, positive values are used for special but normal events.
  */
  Z_OK:               0,
  Z_STREAM_END:       1,
  Z_NEED_DICT:        2,
  Z_ERRNO:           -1,
  Z_STREAM_ERROR:    -2,
  Z_DATA_ERROR:      -3,
  //Z_MEM_ERROR:     -4,
  Z_BUF_ERROR:       -5,
  //Z_VERSION_ERROR: -6,

  /* compression levels */
  Z_NO_COMPRESSION:         0,
  Z_BEST_SPEED:             1,
  Z_BEST_COMPRESSION:       9,
  Z_DEFAULT_COMPRESSION:   -1,


  Z_FILTERED:               1,
  Z_HUFFMAN_ONLY:           2,
  Z_RLE:                    3,
  Z_FIXED:                  4,
  Z_DEFAULT_STRATEGY:       0,

  /* Possible values of the data_type field (though see inflate()) */
  Z_BINARY:                 0,
  Z_TEXT:                   1,
  //Z_ASCII:                1, // = Z_TEXT (deprecated)
  Z_UNKNOWN:                2,

  /* The deflate compression method */
  Z_DEFLATED:               8
  //Z_NULL:                 null // Use -1 or null inline, depending on var type
};

},{}],5:[function(require,module,exports){
'use strict';

// Note: we can't get significant speed boost here.
// So write code to minimize size - no pregenerated tables
// and array tools dependencies.

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

// Use ordinary array, since untyped makes no boost here
function makeTable() {
  var c, table = [];

  for (var n = 0; n < 256; n++) {
    c = n;
    for (var k = 0; k < 8; k++) {
      c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
    }
    table[n] = c;
  }

  return table;
}

// Create table on load. Just 255 signed longs. Not a problem.
var crcTable = makeTable();


function crc32(crc, buf, len, pos) {
  var t = crcTable,
      end = pos + len;

  crc ^= -1;

  for (var i = pos; i < end; i++) {
    crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF];
  }

  return (crc ^ (-1)); // >>> 0;
}


module.exports = crc32;

},{}],6:[function(require,module,exports){
'use strict';

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

function GZheader() {
  /* true if compressed data believed to be text */
  this.text       = 0;
  /* modification time */
  this.time       = 0;
  /* extra flags (not used when writing a gzip file) */
  this.xflags     = 0;
  /* operating system */
  this.os         = 0;
  /* pointer to extra field or Z_NULL if none */
  this.extra      = null;
  /* extra field length (valid if extra != Z_NULL) */
  this.extra_len  = 0; // Actually, we don't need it in JS,
                       // but leave for few code modifications

  //
  // Setup limits is not necessary because in js we should not preallocate memory
  // for inflate use constant limit in 65536 bytes
  //

  /* space at extra (only when reading header) */
  // this.extra_max  = 0;
  /* pointer to zero-terminated file name or Z_NULL */
  this.name       = '';
  /* space at name (only when reading header) */
  // this.name_max   = 0;
  /* pointer to zero-terminated comment or Z_NULL */
  this.comment    = '';
  /* space at comment (only when reading header) */
  // this.comm_max   = 0;
  /* true if there was or will be a header crc */
  this.hcrc       = 0;
  /* true when done reading gzip header (not used when writing a gzip file) */
  this.done       = false;
}

module.exports = GZheader;

},{}],7:[function(require,module,exports){
'use strict';

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

// See state defs from inflate.js
var BAD = 30;       /* got a data error -- remain here until reset */
var TYPE = 12;      /* i: waiting for type bits, including last-flag bit */

/*
   Decode literal, length, and distance codes and write out the resulting
   literal and match bytes until either not enough input or output is
   available, an end-of-block is encountered, or a data error is encountered.
   When large enough input and output buffers are supplied to inflate(), for
   example, a 16K input buffer and a 64K output buffer, more than 95% of the
   inflate execution time is spent in this routine.

   Entry assumptions:

        state.mode === LEN
        strm.avail_in >= 6
        strm.avail_out >= 258
        start >= strm.avail_out
        state.bits < 8

   On return, state.mode is one of:

        LEN -- ran out of enough output space or enough available input
        TYPE -- reached end of block code, inflate() to interpret next block
        BAD -- error in block data

   Notes:

    - The maximum input bits used by a length/distance pair is 15 bits for the
      length code, 5 bits for the length extra, 15 bits for the distance code,
      and 13 bits for the distance extra.  This totals 48 bits, or six bytes.
      Therefore if strm.avail_in >= 6, then there is enough input to avoid
      checking for available input while decoding.

    - The maximum bytes that a single length/distance pair can output is 258
      bytes, which is the maximum length that can be coded.  inflate_fast()
      requires strm.avail_out >= 258 for each loop to avoid checking for
      output space.
 */
module.exports = function inflate_fast(strm, start) {
  var state;
  var _in;                    /* local strm.input */
  var last;                   /* have enough input while in < last */
  var _out;                   /* local strm.output */
  var beg;                    /* inflate()'s initial strm.output */
  var end;                    /* while out < end, enough space available */
//#ifdef INFLATE_STRICT
  var dmax;                   /* maximum distance from zlib header */
//#endif
  var wsize;                  /* window size or zero if not using window */
  var whave;                  /* valid bytes in the window */
  var wnext;                  /* window write index */
  // Use `s_window` instead `window`, avoid conflict with instrumentation tools
  var s_window;               /* allocated sliding window, if wsize != 0 */
  var hold;                   /* local strm.hold */
  var bits;                   /* local strm.bits */
  var lcode;                  /* local strm.lencode */
  var dcode;                  /* local strm.distcode */
  var lmask;                  /* mask for first level of length codes */
  var dmask;                  /* mask for first level of distance codes */
  var here;                   /* retrieved table entry */
  var op;                     /* code bits, operation, extra bits, or */
                              /*  window position, window bytes to copy */
  var len;                    /* match length, unused bytes */
  var dist;                   /* match distance */
  var from;                   /* where to copy match from */
  var from_source;


  var input, output; // JS specific, because we have no pointers

  /* copy state to local variables */
  state = strm.state;
  //here = state.here;
  _in = strm.next_in;
  input = strm.input;
  last = _in + (strm.avail_in - 5);
  _out = strm.next_out;
  output = strm.output;
  beg = _out - (start - strm.avail_out);
  end = _out + (strm.avail_out - 257);
//#ifdef INFLATE_STRICT
  dmax = state.dmax;
//#endif
  wsize = state.wsize;
  whave = state.whave;
  wnext = state.wnext;
  s_window = state.window;
  hold = state.hold;
  bits = state.bits;
  lcode = state.lencode;
  dcode = state.distcode;
  lmask = (1 << state.lenbits) - 1;
  dmask = (1 << state.distbits) - 1;


  /* decode literals and length/distances until end-of-block or not enough
     input data or output space */

  top:
  do {
    if (bits < 15) {
      hold += input[_in++] << bits;
      bits += 8;
      hold += input[_in++] << bits;
      bits += 8;
    }

    here = lcode[hold & lmask];

    dolen:
    for (;;) { // Goto emulation
      op = here >>> 24/*here.bits*/;
      hold >>>= op;
      bits -= op;
      op = (here >>> 16) & 0xff/*here.op*/;
      if (op === 0) {                          /* literal */
        //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
        //        "inflate:         literal '%c'\n" :
        //        "inflate:         literal 0x%02x\n", here.val));
        output[_out++] = here & 0xffff/*here.val*/;
      }
      else if (op & 16) {                     /* length base */
        len = here & 0xffff/*here.val*/;
        op &= 15;                           /* number of extra bits */
        if (op) {
          if (bits < op) {
            hold += input[_in++] << bits;
            bits += 8;
          }
          len += hold & ((1 << op) - 1);
          hold >>>= op;
          bits -= op;
        }
        //Tracevv((stderr, "inflate:         length %u\n", len));
        if (bits < 15) {
          hold += input[_in++] << bits;
          bits += 8;
          hold += input[_in++] << bits;
          bits += 8;
        }
        here = dcode[hold & dmask];

        dodist:
        for (;;) { // goto emulation
          op = here >>> 24/*here.bits*/;
          hold >>>= op;
          bits -= op;
          op = (here >>> 16) & 0xff/*here.op*/;

          if (op & 16) {                      /* distance base */
            dist = here & 0xffff/*here.val*/;
            op &= 15;                       /* number of extra bits */
            if (bits < op) {
              hold += input[_in++] << bits;
              bits += 8;
              if (bits < op) {
                hold += input[_in++] << bits;
                bits += 8;
              }
            }
            dist += hold & ((1 << op) - 1);
//#ifdef INFLATE_STRICT
            if (dist > dmax) {
              strm.msg = 'invalid distance too far back';
              state.mode = BAD;
              break top;
            }
//#endif
            hold >>>= op;
            bits -= op;
            //Tracevv((stderr, "inflate:         distance %u\n", dist));
            op = _out - beg;                /* max distance in output */
            if (dist > op) {                /* see if copy from window */
              op = dist - op;               /* distance back in window */
              if (op > whave) {
                if (state.sane) {
                  strm.msg = 'invalid distance too far back';
                  state.mode = BAD;
                  break top;
                }

// (!) This block is disabled in zlib defaults,
// don't enable it for binary compatibility
//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
//                if (len <= op - whave) {
//                  do {
//                    output[_out++] = 0;
//                  } while (--len);
//                  continue top;
//                }
//                len -= op - whave;
//                do {
//                  output[_out++] = 0;
//                } while (--op > whave);
//                if (op === 0) {
//                  from = _out - dist;
//                  do {
//                    output[_out++] = output[from++];
//                  } while (--len);
//                  continue top;
//                }
//#endif
              }
              from = 0; // window index
              from_source = s_window;
              if (wnext === 0) {           /* very common case */
                from += wsize - op;
                if (op < len) {         /* some from window */
                  len -= op;
                  do {
                    output[_out++] = s_window[from++];
                  } while (--op);
                  from = _out - dist;  /* rest from output */
                  from_source = output;
                }
              }
              else if (wnext < op) {      /* wrap around window */
                from += wsize + wnext - op;
                op -= wnext;
                if (op < len) {         /* some from end of window */
                  len -= op;
                  do {
                    output[_out++] = s_window[from++];
                  } while (--op);
                  from = 0;
                  if (wnext < len) {  /* some from start of window */
                    op = wnext;
                    len -= op;
                    do {
                      output[_out++] = s_window[from++];
                    } while (--op);
                    from = _out - dist;      /* rest from output */
                    from_source = output;
                  }
                }
              }
              else {                      /* contiguous in window */
                from += wnext - op;
                if (op < len) {         /* some from window */
                  len -= op;
                  do {
                    output[_out++] = s_window[from++];
                  } while (--op);
                  from = _out - dist;  /* rest from output */
                  from_source = output;
                }
              }
              while (len > 2) {
                output[_out++] = from_source[from++];
                output[_out++] = from_source[from++];
                output[_out++] = from_source[from++];
                len -= 3;
              }
              if (len) {
                output[_out++] = from_source[from++];
                if (len > 1) {
                  output[_out++] = from_source[from++];
                }
              }
            }
            else {
              from = _out - dist;          /* copy direct from output */
              do {                        /* minimum length is three */
                output[_out++] = output[from++];
                output[_out++] = output[from++];
                output[_out++] = output[from++];
                len -= 3;
              } while (len > 2);
              if (len) {
                output[_out++] = output[from++];
                if (len > 1) {
                  output[_out++] = output[from++];
                }
              }
            }
          }
          else if ((op & 64) === 0) {          /* 2nd level distance code */
            here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
            continue dodist;
          }
          else {
            strm.msg = 'invalid distance code';
            state.mode = BAD;
            break top;
          }

          break; // need to emulate goto via "continue"
        }
      }
      else if ((op & 64) === 0) {              /* 2nd level length code */
        here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
        continue dolen;
      }
      else if (op & 32) {                     /* end-of-block */
        //Tracevv((stderr, "inflate:         end of block\n"));
        state.mode = TYPE;
        break top;
      }
      else {
        strm.msg = 'invalid literal/length code';
        state.mode = BAD;
        break top;
      }

      break; // need to emulate goto via "continue"
    }
  } while (_in < last && _out < end);

  /* return unused bytes (on entry, bits < 8, so in won't go too far back) */
  len = bits >> 3;
  _in -= len;
  bits -= len << 3;
  hold &= (1 << bits) - 1;

  /* update state and return */
  strm.next_in = _in;
  strm.next_out = _out;
  strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last));
  strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end));
  state.hold = hold;
  state.bits = bits;
  return;
};

},{}],8:[function(require,module,exports){
'use strict';

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

var utils         = require('../utils/common');
var adler32       = require('./adler32');
var crc32         = require('./crc32');
var inflate_fast  = require('./inffast');
var inflate_table = require('./inftrees');

var CODES = 0;
var LENS = 1;
var DISTS = 2;

/* Public constants ==========================================================*/
/* ===========================================================================*/


/* Allowed flush values; see deflate() and inflate() below for details */
//var Z_NO_FLUSH      = 0;
//var Z_PARTIAL_FLUSH = 1;
//var Z_SYNC_FLUSH    = 2;
//var Z_FULL_FLUSH    = 3;
var Z_FINISH        = 4;
var Z_BLOCK         = 5;
var Z_TREES         = 6;


/* Return codes for the compression/decompression functions. Negative values
 * are errors, positive values are used for special but normal events.
 */
var Z_OK            = 0;
var Z_STREAM_END    = 1;
var Z_NEED_DICT     = 2;
//var Z_ERRNO         = -1;
var Z_STREAM_ERROR  = -2;
var Z_DATA_ERROR    = -3;
var Z_MEM_ERROR     = -4;
var Z_BUF_ERROR     = -5;
//var Z_VERSION_ERROR = -6;

/* The deflate compression method */
var Z_DEFLATED  = 8;


/* STATES ====================================================================*/
/* ===========================================================================*/


var    HEAD = 1;       /* i: waiting for magic header */
var    FLAGS = 2;      /* i: waiting for method and flags (gzip) */
var    TIME = 3;       /* i: waiting for modification time (gzip) */
var    OS = 4;         /* i: waiting for extra flags and operating system (gzip) */
var    EXLEN = 5;      /* i: waiting for extra length (gzip) */
var    EXTRA = 6;      /* i: waiting for extra bytes (gzip) */
var    NAME = 7;       /* i: waiting for end of file name (gzip) */
var    COMMENT = 8;    /* i: waiting for end of comment (gzip) */
var    HCRC = 9;       /* i: waiting for header crc (gzip) */
var    DICTID = 10;    /* i: waiting for dictionary check value */
var    DICT = 11;      /* waiting for inflateSetDictionary() call */
var        TYPE = 12;      /* i: waiting for type bits, including last-flag bit */
var        TYPEDO = 13;    /* i: same, but skip check to exit inflate on new block */
var        STORED = 14;    /* i: waiting for stored size (length and complement) */
var        COPY_ = 15;     /* i/o: same as COPY below, but only first time in */
var        COPY = 16;      /* i/o: waiting for input or output to copy stored block */
var        TABLE = 17;     /* i: waiting for dynamic block table lengths */
var        LENLENS = 18;   /* i: waiting for code length code lengths */
var        CODELENS = 19;  /* i: waiting for length/lit and distance code lengths */
var            LEN_ = 20;      /* i: same as LEN below, but only first time in */
var            LEN = 21;       /* i: waiting for length/lit/eob code */
var            LENEXT = 22;    /* i: waiting for length extra bits */
var            DIST = 23;      /* i: waiting for distance code */
var            DISTEXT = 24;   /* i: waiting for distance extra bits */
var            MATCH = 25;     /* o: waiting for output space to copy string */
var            LIT = 26;       /* o: waiting for output space to write literal */
var    CHECK = 27;     /* i: waiting for 32-bit check value */
var    LENGTH = 28;    /* i: waiting for 32-bit length (gzip) */
var    DONE = 29;      /* finished check, done -- remain here until reset */
var    BAD = 30;       /* got a data error -- remain here until reset */
var    MEM = 31;       /* got an inflate() memory error -- remain here until reset */
var    SYNC = 32;      /* looking for synchronization bytes to restart inflate() */

/* ===========================================================================*/



var ENOUGH_LENS = 852;
var ENOUGH_DISTS = 592;
//var ENOUGH =  (ENOUGH_LENS+ENOUGH_DISTS);

var MAX_WBITS = 15;
/* 32K LZ77 window */
var DEF_WBITS = MAX_WBITS;


function zswap32(q) {
  return  (((q >>> 24) & 0xff) +
          ((q >>> 8) & 0xff00) +
          ((q & 0xff00) << 8) +
          ((q & 0xff) << 24));
}


function InflateState() {
  this.mode = 0;             /* current inflate mode */
  this.last = false;          /* true if processing last block */
  this.wrap = 0;              /* bit 0 true for zlib, bit 1 true for gzip */
  this.havedict = false;      /* true if dictionary provided */
  this.flags = 0;             /* gzip header method and flags (0 if zlib) */
  this.dmax = 0;              /* zlib header max distance (INFLATE_STRICT) */
  this.check = 0;             /* protected copy of check value */
  this.total = 0;             /* protected copy of output count */
  // TODO: may be {}
  this.head = null;           /* where to save gzip header information */

  /* sliding window */
  this.wbits = 0;             /* log base 2 of requested window size */
  this.wsize = 0;             /* window size or zero if not using window */
  this.whave = 0;             /* valid bytes in the window */
  this.wnext = 0;             /* window write index */
  this.window = null;         /* allocated sliding window, if needed */

  /* bit accumulator */
  this.hold = 0;              /* input bit accumulator */
  this.bits = 0;              /* number of bits in "in" */

  /* for string and stored block copying */
  this.length = 0;            /* literal or length of data to copy */
  this.offset = 0;            /* distance back to copy string from */

  /* for table and code decoding */
  this.extra = 0;             /* extra bits needed */

  /* fixed and dynamic code tables */
  this.lencode = null;          /* starting table for length/literal codes */
  this.distcode = null;         /* starting table for distance codes */
  this.lenbits = 0;           /* index bits for lencode */
  this.distbits = 0;          /* index bits for distcode */

  /* dynamic table building */
  this.ncode = 0;             /* number of code length code lengths */
  this.nlen = 0;              /* number of length code lengths */
  this.ndist = 0;             /* number of distance code lengths */
  this.have = 0;              /* number of code lengths in lens[] */
  this.next = null;              /* next available space in codes[] */

  this.lens = new utils.Buf16(320); /* temporary storage for code lengths */
  this.work = new utils.Buf16(288); /* work area for code table building */

  /*
   because we don't have pointers in js, we use lencode and distcode directly
   as buffers so we don't need codes
  */
  //this.codes = new utils.Buf32(ENOUGH);       /* space for code tables */
  this.lendyn = null;              /* dynamic table for length/literal codes (JS specific) */
  this.distdyn = null;             /* dynamic table for distance codes (JS specific) */
  this.sane = 0;                   /* if false, allow invalid distance too far */
  this.back = 0;                   /* bits back of last unprocessed length/lit */
  this.was = 0;                    /* initial length of match */
}

function inflateResetKeep(strm) {
  var state;

  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
  state = strm.state;
  strm.total_in = strm.total_out = state.total = 0;
  strm.msg = ''; /*Z_NULL*/
  if (state.wrap) {       /* to support ill-conceived Java test suite */
    strm.adler = state.wrap & 1;
  }
  state.mode = HEAD;
  state.last = 0;
  state.havedict = 0;
  state.dmax = 32768;
  state.head = null/*Z_NULL*/;
  state.hold = 0;
  state.bits = 0;
  //state.lencode = state.distcode = state.next = state.codes;
  state.lencode = state.lendyn = new utils.Buf32(ENOUGH_LENS);
  state.distcode = state.distdyn = new utils.Buf32(ENOUGH_DISTS);

  state.sane = 1;
  state.back = -1;
  //Tracev((stderr, "inflate: reset\n"));
  return Z_OK;
}

function inflateReset(strm) {
  var state;

  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
  state = strm.state;
  state.wsize = 0;
  state.whave = 0;
  state.wnext = 0;
  return inflateResetKeep(strm);

}

function inflateReset2(strm, windowBits) {
  var wrap;
  var state;

  /* get the state */
  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
  state = strm.state;

  /* extract wrap request from windowBits parameter */
  if (windowBits < 0) {
    wrap = 0;
    windowBits = -windowBits;
  }
  else {
    wrap = (windowBits >> 4) + 1;
    if (windowBits < 48) {
      windowBits &= 15;
    }
  }

  /* set number of window bits, free window if different */
  if (windowBits && (windowBits < 8 || windowBits > 15)) {
    return Z_STREAM_ERROR;
  }
  if (state.window !== null && state.wbits !== windowBits) {
    state.window = null;
  }

  /* update state and reset the rest of it */
  state.wrap = wrap;
  state.wbits = windowBits;
  return inflateReset(strm);
}

function inflateInit2(strm, windowBits) {
  var ret;
  var state;

  if (!strm) { return Z_STREAM_ERROR; }
  //strm.msg = Z_NULL;                 /* in case we return an error */

  state = new InflateState();

  //if (state === Z_NULL) return Z_MEM_ERROR;
  //Tracev((stderr, "inflate: allocated\n"));
  strm.state = state;
  state.window = null/*Z_NULL*/;
  ret = inflateReset2(strm, windowBits);
  if (ret !== Z_OK) {
    strm.state = null/*Z_NULL*/;
  }
  return ret;
}

function inflateInit(strm) {
  return inflateInit2(strm, DEF_WBITS);
}


/*
 Return state with length and distance decoding tables and index sizes set to
 fixed code decoding.  Normally this returns fixed tables from inffixed.h.
 If BUILDFIXED is defined, then instead this routine builds the tables the
 first time it's called, and returns those tables the first time and
 thereafter.  This reduces the size of the code by about 2K bytes, in
 exchange for a little execution time.  However, BUILDFIXED should not be
 used for threaded applications, since the rewriting of the tables and virgin
 may not be thread-safe.
 */
var virgin = true;

var lenfix, distfix; // We have no pointers in JS, so keep tables separate

function fixedtables(state) {
  /* build fixed huffman tables if first call (may not be thread safe) */
  if (virgin) {
    var sym;

    lenfix = new utils.Buf32(512);
    distfix = new utils.Buf32(32);

    /* literal/length table */
    sym = 0;
    while (sym < 144) { state.lens[sym++] = 8; }
    while (sym < 256) { state.lens[sym++] = 9; }
    while (sym < 280) { state.lens[sym++] = 7; }
    while (sym < 288) { state.lens[sym++] = 8; }

    inflate_table(LENS,  state.lens, 0, 288, lenfix,   0, state.work, { bits: 9 });

    /* distance table */
    sym = 0;
    while (sym < 32) { state.lens[sym++] = 5; }

    inflate_table(DISTS, state.lens, 0, 32,   distfix, 0, state.work, { bits: 5 });

    /* do this just once */
    virgin = false;
  }

  state.lencode = lenfix;
  state.lenbits = 9;
  state.distcode = distfix;
  state.distbits = 5;
}


/*
 Update the window with the last wsize (normally 32K) bytes written before
 returning.  If window does not exist yet, create it.  This is only called
 when a window is already in use, or when output has been written during this
 inflate call, but the end of the deflate stream has not been reached yet.
 It is also called to create a window for dictionary data when a dictionary
 is loaded.

 Providing output buffers larger than 32K to inflate() should provide a speed
 advantage, since only the last 32K of output is copied to the sliding window
 upon return from inflate(), and since all distances after the first 32K of
 output will fall in the output data, making match copies simpler and faster.
 The advantage may be dependent on the size of the processor's data caches.
 */
function updatewindow(strm, src, end, copy) {
  var dist;
  var state = strm.state;

  /* if it hasn't been done already, allocate space for the window */
  if (state.window === null) {
    state.wsize = 1 << state.wbits;
    state.wnext = 0;
    state.whave = 0;

    state.window = new utils.Buf8(state.wsize);
  }

  /* copy state->wsize or less output bytes into the circular window */
  if (copy >= state.wsize) {
    utils.arraySet(state.window, src, end - state.wsize, state.wsize, 0);
    state.wnext = 0;
    state.whave = state.wsize;
  }
  else {
    dist = state.wsize - state.wnext;
    if (dist > copy) {
      dist = copy;
    }
    //zmemcpy(state->window + state->wnext, end - copy, dist);
    utils.arraySet(state.window, src, end - copy, dist, state.wnext);
    copy -= dist;
    if (copy) {
      //zmemcpy(state->window, end - copy, copy);
      utils.arraySet(state.window, src, end - copy, copy, 0);
      state.wnext = copy;
      state.whave = state.wsize;
    }
    else {
      state.wnext += dist;
      if (state.wnext === state.wsize) { state.wnext = 0; }
      if (state.whave < state.wsize) { state.whave += dist; }
    }
  }
  return 0;
}

function inflate(strm, flush) {
  var state;
  var input, output;          // input/output buffers
  var next;                   /* next input INDEX */
  var put;                    /* next output INDEX */
  var have, left;             /* available input and output */
  var hold;                   /* bit buffer */
  var bits;                   /* bits in bit buffer */
  var _in, _out;              /* save starting available input and output */
  var copy;                   /* number of stored or match bytes to copy */
  var from;                   /* where to copy match bytes from */
  var from_source;
  var here = 0;               /* current decoding table entry */
  var here_bits, here_op, here_val; // paked "here" denormalized (JS specific)
  //var last;                   /* parent table entry */
  var last_bits, last_op, last_val; // paked "last" denormalized (JS specific)
  var len;                    /* length to copy for repeats, bits to drop */
  var ret;                    /* return code */
  var hbuf = new utils.Buf8(4);    /* buffer for gzip header crc calculation */
  var opts;

  var n; // temporary var for NEED_BITS

  var order = /* permutation of code lengths */
    [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ];


  if (!strm || !strm.state || !strm.output ||
      (!strm.input && strm.avail_in !== 0)) {
    return Z_STREAM_ERROR;
  }

  state = strm.state;
  if (state.mode === TYPE) { state.mode = TYPEDO; }    /* skip check */


  //--- LOAD() ---
  put = strm.next_out;
  output = strm.output;
  left = strm.avail_out;
  next = strm.next_in;
  input = strm.input;
  have = strm.avail_in;
  hold = state.hold;
  bits = state.bits;
  //---

  _in = have;
  _out = left;
  ret = Z_OK;

  inf_leave: // goto emulation
  for (;;) {
    switch (state.mode) {
      case HEAD:
        if (state.wrap === 0) {
          state.mode = TYPEDO;
          break;
        }
        //=== NEEDBITS(16);
        while (bits < 16) {
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
        }
        //===//
        if ((state.wrap & 2) && hold === 0x8b1f) {  /* gzip header */
          state.check = 0/*crc32(0L, Z_NULL, 0)*/;
          //=== CRC2(state.check, hold);
          hbuf[0] = hold & 0xff;
          hbuf[1] = (hold >>> 8) & 0xff;
          state.check = crc32(state.check, hbuf, 2, 0);
          //===//

          //=== INITBITS();
          hold = 0;
          bits = 0;
          //===//
          state.mode = FLAGS;
          break;
        }
        state.flags = 0;           /* expect zlib header */
        if (state.head) {
          state.head.done = false;
        }
        if (!(state.wrap & 1) ||   /* check if zlib header allowed */
          (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) {
          strm.msg = 'incorrect header check';
          state.mode = BAD;
          break;
        }
        if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED) {
          strm.msg = 'unknown compression method';
          state.mode = BAD;
          break;
        }
        //--- DROPBITS(4) ---//
        hold >>>= 4;
        bits -= 4;
        //---//
        len = (hold & 0x0f)/*BITS(4)*/ + 8;
        if (state.wbits === 0) {
          state.wbits = len;
        }
        else if (len > state.wbits) {
          strm.msg = 'invalid window size';
          state.mode = BAD;
          break;
        }
        state.dmax = 1 << len;
        //Tracev((stderr, "inflate:   zlib header ok\n"));
        strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/;
        state.mode = hold & 0x200 ? DICTID : TYPE;
        //=== INITBITS();
        hold = 0;
        bits = 0;
        //===//
        break;
      case FLAGS:
        //=== NEEDBITS(16); */
        while (bits < 16) {
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
        }
        //===//
        state.flags = hold;
        if ((state.flags & 0xff) !== Z_DEFLATED) {
          strm.msg = 'unknown compression method';
          state.mode = BAD;
          break;
        }
        if (state.flags & 0xe000) {
          strm.msg = 'unknown header flags set';
          state.mode = BAD;
          break;
        }
        if (state.head) {
          state.head.text = ((hold >> 8) & 1);
        }
        if (state.flags & 0x0200) {
          //=== CRC2(state.check, hold);
          hbuf[0] = hold & 0xff;
          hbuf[1] = (hold >>> 8) & 0xff;
          state.check = crc32(state.check, hbuf, 2, 0);
          //===//
        }
        //=== INITBITS();
        hold = 0;
        bits = 0;
        //===//
        state.mode = TIME;
        /* falls through */
      case TIME:
        //=== NEEDBITS(32); */
        while (bits < 32) {
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
        }
        //===//
        if (state.head) {
          state.head.time = hold;
        }
        if (state.flags & 0x0200) {
          //=== CRC4(state.check, hold)
          hbuf[0] = hold & 0xff;
          hbuf[1] = (hold >>> 8) & 0xff;
          hbuf[2] = (hold >>> 16) & 0xff;
          hbuf[3] = (hold >>> 24) & 0xff;
          state.check = crc32(state.check, hbuf, 4, 0);
          //===
        }
        //=== INITBITS();
        hold = 0;
        bits = 0;
        //===//
        state.mode = OS;
        /* falls through */
      case OS:
        //=== NEEDBITS(16); */
        while (bits < 16) {
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
        }
        //===//
        if (state.head) {
          state.head.xflags = (hold & 0xff);
          state.head.os = (hold >> 8);
        }
        if (state.flags & 0x0200) {
          //=== CRC2(state.check, hold);
          hbuf[0] = hold & 0xff;
          hbuf[1] = (hold >>> 8) & 0xff;
          state.check = crc32(state.check, hbuf, 2, 0);
          //===//
        }
        //=== INITBITS();
        hold = 0;
        bits = 0;
        //===//
        state.mode = EXLEN;
        /* falls through */
      case EXLEN:
        if (state.flags & 0x0400) {
          //=== NEEDBITS(16); */
          while (bits < 16) {
            if (have === 0) { break inf_leave; }
            have--;
            hold += input[next++] << bits;
            bits += 8;
          }
          //===//
          state.length = hold;
          if (state.head) {
            state.head.extra_len = hold;
          }
          if (state.flags & 0x0200) {
            //=== CRC2(state.check, hold);
            hbuf[0] = hold & 0xff;
            hbuf[1] = (hold >>> 8) & 0xff;
            state.check = crc32(state.check, hbuf, 2, 0);
            //===//
          }
          //=== INITBITS();
          hold = 0;
          bits = 0;
          //===//
        }
        else if (state.head) {
          state.head.extra = null/*Z_NULL*/;
        }
        state.mode = EXTRA;
        /* falls through */
      case EXTRA:
        if (state.flags & 0x0400) {
          copy = state.length;
          if (copy > have) { copy = have; }
          if (copy) {
            if (state.head) {
              len = state.head.extra_len - state.length;
              if (!state.head.extra) {
                // Use untyped array for more convenient processing later
                state.head.extra = new Array(state.head.extra_len);
              }
              utils.arraySet(
                state.head.extra,
                input,
                next,
                // extra field is limited to 65536 bytes
                // - no need for additional size check
                copy,
                /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/
                len
              );
              //zmemcpy(state.head.extra + len, next,
              //        len + copy > state.head.extra_max ?
              //        state.head.extra_max - len : copy);
            }
            if (state.flags & 0x0200) {
              state.check = crc32(state.check, input, copy, next);
            }
            have -= copy;
            next += copy;
            state.length -= copy;
          }
          if (state.length) { break inf_leave; }
        }
        state.length = 0;
        state.mode = NAME;
        /* falls through */
      case NAME:
        if (state.flags & 0x0800) {
          if (have === 0) { break inf_leave; }
          copy = 0;
          do {
            // TODO: 2 or 1 bytes?
            len = input[next + copy++];
            /* use constant limit because in js we should not preallocate memory */
            if (state.head && len &&
                (state.length < 65536 /*state.head.name_max*/)) {
              state.head.name += String.fromCharCode(len);
            }
          } while (len && copy < have);

          if (state.flags & 0x0200) {
            state.check = crc32(state.check, input, copy, next);
          }
          have -= copy;
          next += copy;
          if (len) { break inf_leave; }
        }
        else if (state.head) {
          state.head.name = null;
        }
        state.length = 0;
        state.mode = COMMENT;
        /* falls through */
      case COMMENT:
        if (state.flags & 0x1000) {
          if (have === 0) { break inf_leave; }
          copy = 0;
          do {
            len = input[next + copy++];
            /* use constant limit because in js we should not preallocate memory */
            if (state.head && len &&
                (state.length < 65536 /*state.head.comm_max*/)) {
              state.head.comment += String.fromCharCode(len);
            }
          } while (len && copy < have);
          if (state.flags & 0x0200) {
            state.check = crc32(state.check, input, copy, next);
          }
          have -= copy;
          next += copy;
          if (len) { break inf_leave; }
        }
        else if (state.head) {
          state.head.comment = null;
        }
        state.mode = HCRC;
        /* falls through */
      case HCRC:
        if (state.flags & 0x0200) {
          //=== NEEDBITS(16); */
          while (bits < 16) {
            if (have === 0) { break inf_leave; }
            have--;
            hold += input[next++] << bits;
            bits += 8;
          }
          //===//
          if (hold !== (state.check & 0xffff)) {
            strm.msg = 'header crc mismatch';
            state.mode = BAD;
            break;
          }
          //=== INITBITS();
          hold = 0;
          bits = 0;
          //===//
        }
        if (state.head) {
          state.head.hcrc = ((state.flags >> 9) & 1);
          state.head.done = true;
        }
        strm.adler = state.check = 0;
        state.mode = TYPE;
        break;
      case DICTID:
        //=== NEEDBITS(32); */
        while (bits < 32) {
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
        }
        //===//
        strm.adler = state.check = zswap32(hold);
        //=== INITBITS();
        hold = 0;
        bits = 0;
        //===//
        state.mode = DICT;
        /* falls through */
      case DICT:
        if (state.havedict === 0) {
          //--- RESTORE() ---
          strm.next_out = put;
          strm.avail_out = left;
          strm.next_in = next;
          strm.avail_in = have;
          state.hold = hold;
          state.bits = bits;
          //---
          return Z_NEED_DICT;
        }
        strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/;
        state.mode = TYPE;
        /* falls through */
      case TYPE:
        if (flush === Z_BLOCK || flush === Z_TREES) { break inf_leave; }
        /* falls through */
      case TYPEDO:
        if (state.last) {
          //--- BYTEBITS() ---//
          hold >>>= bits & 7;
          bits -= bits & 7;
          //---//
          state.mode = CHECK;
          break;
        }
        //=== NEEDBITS(3); */
        while (bits < 3) {
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
        }
        //===//
        state.last = (hold & 0x01)/*BITS(1)*/;
        //--- DROPBITS(1) ---//
        hold >>>= 1;
        bits -= 1;
        //---//

        switch ((hold & 0x03)/*BITS(2)*/) {
          case 0:                             /* stored block */
            //Tracev((stderr, "inflate:     stored block%s\n",
            //        state.last ? " (last)" : ""));
            state.mode = STORED;
            break;
          case 1:                             /* fixed block */
            fixedtables(state);
            //Tracev((stderr, "inflate:     fixed codes block%s\n",
            //        state.last ? " (last)" : ""));
            state.mode = LEN_;             /* decode codes */
            if (flush === Z_TREES) {
              //--- DROPBITS(2) ---//
              hold >>>= 2;
              bits -= 2;
              //---//
              break inf_leave;
            }
            break;
          case 2:                             /* dynamic block */
            //Tracev((stderr, "inflate:     dynamic codes block%s\n",
            //        state.last ? " (last)" : ""));
            state.mode = TABLE;
            break;
          case 3:
            strm.msg = 'invalid block type';
            state.mode = BAD;
        }
        //--- DROPBITS(2) ---//
        hold >>>= 2;
        bits -= 2;
        //---//
        break;
      case STORED:
        //--- BYTEBITS() ---// /* go to byte boundary */
        hold >>>= bits & 7;
        bits -= bits & 7;
        //---//
        //=== NEEDBITS(32); */
        while (bits < 32) {
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
        }
        //===//
        if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) {
          strm.msg = 'invalid stored block lengths';
          state.mode = BAD;
          break;
        }
        state.length = hold & 0xffff;
        //Tracev((stderr, "inflate:       stored length %u\n",
        //        state.length));
        //=== INITBITS();
        hold = 0;
        bits = 0;
        //===//
        state.mode = COPY_;
        if (flush === Z_TREES) { break inf_leave; }
        /* falls through */
      case COPY_:
        state.mode = COPY;
        /* falls through */
      case COPY:
        copy = state.length;
        if (copy) {
          if (copy > have) { copy = have; }
          if (copy > left) { copy = left; }
          if (copy === 0) { break inf_leave; }
          //--- zmemcpy(put, next, copy); ---
          utils.arraySet(output, input, next, copy, put);
          //---//
          have -= copy;
          next += copy;
          left -= copy;
          put += copy;
          state.length -= copy;
          break;
        }
        //Tracev((stderr, "inflate:       stored end\n"));
        state.mode = TYPE;
        break;
      case TABLE:
        //=== NEEDBITS(14); */
        while (bits < 14) {
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
        }
        //===//
        state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257;
        //--- DROPBITS(5) ---//
        hold >>>= 5;
        bits -= 5;
        //---//
        state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1;
        //--- DROPBITS(5) ---//
        hold >>>= 5;
        bits -= 5;
        //---//
        state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4;
        //--- DROPBITS(4) ---//
        hold >>>= 4;
        bits -= 4;
        //---//
//#ifndef PKZIP_BUG_WORKAROUND
        if (state.nlen > 286 || state.ndist > 30) {
          strm.msg = 'too many length or distance symbols';
          state.mode = BAD;
          break;
        }
//#endif
        //Tracev((stderr, "inflate:       table sizes ok\n"));
        state.have = 0;
        state.mode = LENLENS;
        /* falls through */
      case LENLENS:
        while (state.have < state.ncode) {
          //=== NEEDBITS(3);
          while (bits < 3) {
            if (have === 0) { break inf_leave; }
            have--;
            hold += input[next++] << bits;
            bits += 8;
          }
          //===//
          state.lens[order[state.have++]] = (hold & 0x07);//BITS(3);
          //--- DROPBITS(3) ---//
          hold >>>= 3;
          bits -= 3;
          //---//
        }
        while (state.have < 19) {
          state.lens[order[state.have++]] = 0;
        }
        // We have separate tables & no pointers. 2 commented lines below not needed.
        //state.next = state.codes;
        //state.lencode = state.next;
        // Switch to use dynamic table
        state.lencode = state.lendyn;
        state.lenbits = 7;

        opts = { bits: state.lenbits };
        ret = inflate_table(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts);
        state.lenbits = opts.bits;

        if (ret) {
          strm.msg = 'invalid code lengths set';
          state.mode = BAD;
          break;
        }
        //Tracev((stderr, "inflate:       code lengths ok\n"));
        state.have = 0;
        state.mode = CODELENS;
        /* falls through */
      case CODELENS:
        while (state.have < state.nlen + state.ndist) {
          for (;;) {
            here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/
            here_bits = here >>> 24;
            here_op = (here >>> 16) & 0xff;
            here_val = here & 0xffff;

            if ((here_bits) <= bits) { break; }
            //--- PULLBYTE() ---//
            if (have === 0) { break inf_leave; }
            have--;
            hold += input[next++] << bits;
            bits += 8;
            //---//
          }
          if (here_val < 16) {
            //--- DROPBITS(here.bits) ---//
            hold >>>= here_bits;
            bits -= here_bits;
            //---//
            state.lens[state.have++] = here_val;
          }
          else {
            if (here_val === 16) {
              //=== NEEDBITS(here.bits + 2);
              n = here_bits + 2;
              while (bits < n) {
                if (have === 0) { break inf_leave; }
                have--;
                hold += input[next++] << bits;
                bits += 8;
              }
              //===//
              //--- DROPBITS(here.bits) ---//
              hold >>>= here_bits;
              bits -= here_bits;
              //---//
              if (state.have === 0) {
                strm.msg = 'invalid bit length repeat';
                state.mode = BAD;
                break;
              }
              len = state.lens[state.have - 1];
              copy = 3 + (hold & 0x03);//BITS(2);
              //--- DROPBITS(2) ---//
              hold >>>= 2;
              bits -= 2;
              //---//
            }
            else if (here_val === 17) {
              //=== NEEDBITS(here.bits + 3);
              n = here_bits + 3;
              while (bits < n) {
                if (have === 0) { break inf_leave; }
                have--;
                hold += input[next++] << bits;
                bits += 8;
              }
              //===//
              //--- DROPBITS(here.bits) ---//
              hold >>>= here_bits;
              bits -= here_bits;
              //---//
              len = 0;
              copy = 3 + (hold & 0x07);//BITS(3);
              //--- DROPBITS(3) ---//
              hold >>>= 3;
              bits -= 3;
              //---//
            }
            else {
              //=== NEEDBITS(here.bits + 7);
              n = here_bits + 7;
              while (bits < n) {
                if (have === 0) { break inf_leave; }
                have--;
                hold += input[next++] << bits;
                bits += 8;
              }
              //===//
              //--- DROPBITS(here.bits) ---//
              hold >>>= here_bits;
              bits -= here_bits;
              //---//
              len = 0;
              copy = 11 + (hold & 0x7f);//BITS(7);
              //--- DROPBITS(7) ---//
              hold >>>= 7;
              bits -= 7;
              //---//
            }
            if (state.have + copy > state.nlen + state.ndist) {
              strm.msg = 'invalid bit length repeat';
              state.mode = BAD;
              break;
            }
            while (copy--) {
              state.lens[state.have++] = len;
            }
          }
        }

        /* handle error breaks in while */
        if (state.mode === BAD) { break; }

        /* check for end-of-block code (better have one) */
        if (state.lens[256] === 0) {
          strm.msg = 'invalid code -- missing end-of-block';
          state.mode = BAD;
          break;
        }

        /* build code tables -- note: do not change the lenbits or distbits
           values here (9 and 6) without reading the comments in inftrees.h
           concerning the ENOUGH constants, which depend on those values */
        state.lenbits = 9;

        opts = { bits: state.lenbits };
        ret = inflate_table(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts);
        // We have separate tables & no pointers. 2 commented lines below not needed.
        // state.next_index = opts.table_index;
        state.lenbits = opts.bits;
        // state.lencode = state.next;

        if (ret) {
          strm.msg = 'invalid literal/lengths set';
          state.mode = BAD;
          break;
        }

        state.distbits = 6;
        //state.distcode.copy(state.codes);
        // Switch to use dynamic table
        state.distcode = state.distdyn;
        opts = { bits: state.distbits };
        ret = inflate_table(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts);
        // We have separate tables & no pointers. 2 commented lines below not needed.
        // state.next_index = opts.table_index;
        state.distbits = opts.bits;
        // state.distcode = state.next;

        if (ret) {
          strm.msg = 'invalid distances set';
          state.mode = BAD;
          break;
        }
        //Tracev((stderr, 'inflate:       codes ok\n'));
        state.mode = LEN_;
        if (flush === Z_TREES) { break inf_leave; }
        /* falls through */
      case LEN_:
        state.mode = LEN;
        /* falls through */
      case LEN:
        if (have >= 6 && left >= 258) {
          //--- RESTORE() ---
          strm.next_out = put;
          strm.avail_out = left;
          strm.next_in = next;
          strm.avail_in = have;
          state.hold = hold;
          state.bits = bits;
          //---
          inflate_fast(strm, _out);
          //--- LOAD() ---
          put = strm.next_out;
          output = strm.output;
          left = strm.avail_out;
          next = strm.next_in;
          input = strm.input;
          have = strm.avail_in;
          hold = state.hold;
          bits = state.bits;
          //---

          if (state.mode === TYPE) {
            state.back = -1;
          }
          break;
        }
        state.back = 0;
        for (;;) {
          here = state.lencode[hold & ((1 << state.lenbits) - 1)];  /*BITS(state.lenbits)*/
          here_bits = here >>> 24;
          here_op = (here >>> 16) & 0xff;
          here_val = here & 0xffff;

          if (here_bits <= bits) { break; }
          //--- PULLBYTE() ---//
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
          //---//
        }
        if (here_op && (here_op & 0xf0) === 0) {
          last_bits = here_bits;
          last_op = here_op;
          last_val = here_val;
          for (;;) {
            here = state.lencode[last_val +
                    ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)];
            here_bits = here >>> 24;
            here_op = (here >>> 16) & 0xff;
            here_val = here & 0xffff;

            if ((last_bits + here_bits) <= bits) { break; }
            //--- PULLBYTE() ---//
            if (have === 0) { break inf_leave; }
            have--;
            hold += input[next++] << bits;
            bits += 8;
            //---//
          }
          //--- DROPBITS(last.bits) ---//
          hold >>>= last_bits;
          bits -= last_bits;
          //---//
          state.back += last_bits;
        }
        //--- DROPBITS(here.bits) ---//
        hold >>>= here_bits;
        bits -= here_bits;
        //---//
        state.back += here_bits;
        state.length = here_val;
        if (here_op === 0) {
          //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
          //        "inflate:         literal '%c'\n" :
          //        "inflate:         literal 0x%02x\n", here.val));
          state.mode = LIT;
          break;
        }
        if (here_op & 32) {
          //Tracevv((stderr, "inflate:         end of block\n"));
          state.back = -1;
          state.mode = TYPE;
          break;
        }
        if (here_op & 64) {
          strm.msg = 'invalid literal/length code';
          state.mode = BAD;
          break;
        }
        state.extra = here_op & 15;
        state.mode = LENEXT;
        /* falls through */
      case LENEXT:
        if (state.extra) {
          //=== NEEDBITS(state.extra);
          n = state.extra;
          while (bits < n) {
            if (have === 0) { break inf_leave; }
            have--;
            hold += input[next++] << bits;
            bits += 8;
          }
          //===//
          state.length += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/;
          //--- DROPBITS(state.extra) ---//
          hold >>>= state.extra;
          bits -= state.extra;
          //---//
          state.back += state.extra;
        }
        //Tracevv((stderr, "inflate:         length %u\n", state.length));
        state.was = state.length;
        state.mode = DIST;
        /* falls through */
      case DIST:
        for (;;) {
          here = state.distcode[hold & ((1 << state.distbits) - 1)];/*BITS(state.distbits)*/
          here_bits = here >>> 24;
          here_op = (here >>> 16) & 0xff;
          here_val = here & 0xffff;

          if ((here_bits) <= bits) { break; }
          //--- PULLBYTE() ---//
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
          //---//
        }
        if ((here_op & 0xf0) === 0) {
          last_bits = here_bits;
          last_op = here_op;
          last_val = here_val;
          for (;;) {
            here = state.distcode[last_val +
                    ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)];
            here_bits = here >>> 24;
            here_op = (here >>> 16) & 0xff;
            here_val = here & 0xffff;

            if ((last_bits + here_bits) <= bits) { break; }
            //--- PULLBYTE() ---//
            if (have === 0) { break inf_leave; }
            have--;
            hold += input[next++] << bits;
            bits += 8;
            //---//
          }
          //--- DROPBITS(last.bits) ---//
          hold >>>= last_bits;
          bits -= last_bits;
          //---//
          state.back += last_bits;
        }
        //--- DROPBITS(here.bits) ---//
        hold >>>= here_bits;
        bits -= here_bits;
        //---//
        state.back += here_bits;
        if (here_op & 64) {
          strm.msg = 'invalid distance code';
          state.mode = BAD;
          break;
        }
        state.offset = here_val;
        state.extra = (here_op) & 15;
        state.mode = DISTEXT;
        /* falls through */
      case DISTEXT:
        if (state.extra) {
          //=== NEEDBITS(state.extra);
          n = state.extra;
          while (bits < n) {
            if (have === 0) { break inf_leave; }
            have--;
            hold += input[next++] << bits;
            bits += 8;
          }
          //===//
          state.offset += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/;
          //--- DROPBITS(state.extra) ---//
          hold >>>= state.extra;
          bits -= state.extra;
          //---//
          state.back += state.extra;
        }
//#ifdef INFLATE_STRICT
        if (state.offset > state.dmax) {
          strm.msg = 'invalid distance too far back';
          state.mode = BAD;
          break;
        }
//#endif
        //Tracevv((stderr, "inflate:         distance %u\n", state.offset));
        state.mode = MATCH;
        /* falls through */
      case MATCH:
        if (left === 0) { break inf_leave; }
        copy = _out - left;
        if (state.offset > copy) {         /* copy from window */
          copy = state.offset - copy;
          if (copy > state.whave) {
            if (state.sane) {
              strm.msg = 'invalid distance too far back';
              state.mode = BAD;
              break;
            }
// (!) This block is disabled in zlib defaults,
// don't enable it for binary compatibility
//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
//          Trace((stderr, "inflate.c too far\n"));
//          copy -= state.whave;
//          if (copy > state.length) { copy = state.length; }
//          if (copy > left) { copy = left; }
//          left -= copy;
//          state.length -= copy;
//          do {
//            output[put++] = 0;
//          } while (--copy);
//          if (state.length === 0) { state.mode = LEN; }
//          break;
//#endif
          }
          if (copy > state.wnext) {
            copy -= state.wnext;
            from = state.wsize - copy;
          }
          else {
            from = state.wnext - copy;
          }
          if (copy > state.length) { copy = state.length; }
          from_source = state.window;
        }
        else {                              /* copy from output */
          from_source = output;
          from = put - state.offset;
          copy = state.length;
        }
        if (copy > left) { copy = left; }
        left -= copy;
        state.length -= copy;
        do {
          output[put++] = from_source[from++];
        } while (--copy);
        if (state.length === 0) { state.mode = LEN; }
        break;
      case LIT:
        if (left === 0) { break inf_leave; }
        output[put++] = state.length;
        left--;
        state.mode = LEN;
        break;
      case CHECK:
        if (state.wrap) {
          //=== NEEDBITS(32);
          while (bits < 32) {
            if (have === 0) { break inf_leave; }
            have--;
            // Use '|' instead of '+' to make sure that result is signed
            hold |= input[next++] << bits;
            bits += 8;
          }
          //===//
          _out -= left;
          strm.total_out += _out;
          state.total += _out;
          if (_out) {
            strm.adler = state.check =
                /*UPDATE(state.check, put - _out, _out);*/
                (state.flags ? crc32(state.check, output, _out, put - _out) : adler32(state.check, output, _out, put - _out));

          }
          _out = left;
          // NB: crc32 stored as signed 32-bit int, zswap32 returns signed too
          if ((state.flags ? hold : zswap32(hold)) !== state.check) {
            strm.msg = 'incorrect data check';
            state.mode = BAD;
            break;
          }
          //=== INITBITS();
          hold = 0;
          bits = 0;
          //===//
          //Tracev((stderr, "inflate:   check matches trailer\n"));
        }
        state.mode = LENGTH;
        /* falls through */
      case LENGTH:
        if (state.wrap && state.flags) {
          //=== NEEDBITS(32);
          while (bits < 32) {
            if (have === 0) { break inf_leave; }
            have--;
            hold += input[next++] << bits;
            bits += 8;
          }
          //===//
          if (hold !== (state.total & 0xffffffff)) {
            strm.msg = 'incorrect length check';
            state.mode = BAD;
            break;
          }
          //=== INITBITS();
          hold = 0;
          bits = 0;
          //===//
          //Tracev((stderr, "inflate:   length matches trailer\n"));
        }
        state.mode = DONE;
        /* falls through */
      case DONE:
        ret = Z_STREAM_END;
        break inf_leave;
      case BAD:
        ret = Z_DATA_ERROR;
        break inf_leave;
      case MEM:
        return Z_MEM_ERROR;
      case SYNC:
        /* falls through */
      default:
        return Z_STREAM_ERROR;
    }
  }

  // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave"

  /*
     Return from inflate(), updating the total counts and the check value.
     If there was no progress during the inflate() call, return a buffer
     error.  Call updatewindow() to create and/or update the window state.
     Note: a memory error from inflate() is non-recoverable.
   */

  //--- RESTORE() ---
  strm.next_out = put;
  strm.avail_out = left;
  strm.next_in = next;
  strm.avail_in = have;
  state.hold = hold;
  state.bits = bits;
  //---

  if (state.wsize || (_out !== strm.avail_out && state.mode < BAD &&
                      (state.mode < CHECK || flush !== Z_FINISH))) {
    if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) {
      state.mode = MEM;
      return Z_MEM_ERROR;
    }
  }
  _in -= strm.avail_in;
  _out -= strm.avail_out;
  strm.total_in += _in;
  strm.total_out += _out;
  state.total += _out;
  if (state.wrap && _out) {
    strm.adler = state.check = /*UPDATE(state.check, strm.next_out - _out, _out);*/
      (state.flags ? crc32(state.check, output, _out, strm.next_out - _out) : adler32(state.check, output, _out, strm.next_out - _out));
  }
  strm.data_type = state.bits + (state.last ? 64 : 0) +
                    (state.mode === TYPE ? 128 : 0) +
                    (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0);
  if (((_in === 0 && _out === 0) || flush === Z_FINISH) && ret === Z_OK) {
    ret = Z_BUF_ERROR;
  }
  return ret;
}

function inflateEnd(strm) {

  if (!strm || !strm.state /*|| strm->zfree == (free_func)0*/) {
    return Z_STREAM_ERROR;
  }

  var state = strm.state;
  if (state.window) {
    state.window = null;
  }
  strm.state = null;
  return Z_OK;
}

function inflateGetHeader(strm, head) {
  var state;

  /* check state */
  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
  state = strm.state;
  if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR; }

  /* save header structure */
  state.head = head;
  head.done = false;
  return Z_OK;
}

function inflateSetDictionary(strm, dictionary) {
  var dictLength = dictionary.length;

  var state;
  var dictid;
  var ret;

  /* check state */
  if (!strm /* == Z_NULL */ || !strm.state /* == Z_NULL */) { return Z_STREAM_ERROR; }
  state = strm.state;

  if (state.wrap !== 0 && state.mode !== DICT) {
    return Z_STREAM_ERROR;
  }

  /* check for correct dictionary identifier */
  if (state.mode === DICT) {
    dictid = 1; /* adler32(0, null, 0)*/
    /* dictid = adler32(dictid, dictionary, dictLength); */
    dictid = adler32(dictid, dictionary, dictLength, 0);
    if (dictid !== state.check) {
      return Z_DATA_ERROR;
    }
  }
  /* copy dictionary to window using updatewindow(), which will amend the
   existing dictionary if appropriate */
  ret = updatewindow(strm, dictionary, dictLength, dictLength);
  if (ret) {
    state.mode = MEM;
    return Z_MEM_ERROR;
  }
  state.havedict = 1;
  // Tracev((stderr, "inflate:   dictionary set\n"));
  return Z_OK;
}

exports.inflateReset = inflateReset;
exports.inflateReset2 = inflateReset2;
exports.inflateResetKeep = inflateResetKeep;
exports.inflateInit = inflateInit;
exports.inflateInit2 = inflateInit2;
exports.inflate = inflate;
exports.inflateEnd = inflateEnd;
exports.inflateGetHeader = inflateGetHeader;
exports.inflateSetDictionary = inflateSetDictionary;
exports.inflateInfo = 'pako inflate (from Nodeca project)';

/* Not implemented
exports.inflateCopy = inflateCopy;
exports.inflateGetDictionary = inflateGetDictionary;
exports.inflateMark = inflateMark;
exports.inflatePrime = inflatePrime;
exports.inflateSync = inflateSync;
exports.inflateSyncPoint = inflateSyncPoint;
exports.inflateUndermine = inflateUndermine;
*/

},{"../utils/common":1,"./adler32":3,"./crc32":5,"./inffast":7,"./inftrees":9}],9:[function(require,module,exports){
'use strict';

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

var utils = require('../utils/common');

var MAXBITS = 15;
var ENOUGH_LENS = 852;
var ENOUGH_DISTS = 592;
//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS);

var CODES = 0;
var LENS = 1;
var DISTS = 2;

var lbase = [ /* Length codes 257..285 base */
  3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
  35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0
];

var lext = [ /* Length codes 257..285 extra */
  16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18,
  19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78
];

var dbase = [ /* Distance codes 0..29 base */
  1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
  257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
  8193, 12289, 16385, 24577, 0, 0
];

var dext = [ /* Distance codes 0..29 extra */
  16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22,
  23, 23, 24, 24, 25, 25, 26, 26, 27, 27,
  28, 28, 29, 29, 64, 64
];

module.exports = function inflate_table(type, lens, lens_index, codes, table, table_index, work, opts)
{
  var bits = opts.bits;
      //here = opts.here; /* table entry for duplication */

  var len = 0;               /* a code's length in bits */
  var sym = 0;               /* index of code symbols */
  var min = 0, max = 0;          /* minimum and maximum code lengths */
  var root = 0;              /* number of index bits for root table */
  var curr = 0;              /* number of index bits for current table */
  var drop = 0;              /* code bits to drop for sub-table */
  var left = 0;                   /* number of prefix codes available */
  var used = 0;              /* code entries in table used */
  var huff = 0;              /* Huffman code */
  var incr;              /* for incrementing code, index */
  var fill;              /* index for replicating entries */
  var low;               /* low bits for current root entry */
  var mask;              /* mask for low root bits */
  var next;             /* next available space in table */
  var base = null;     /* base value table to use */
  var base_index = 0;
//  var shoextra;    /* extra bits table to use */
  var end;                    /* use base and extra for symbol > end */
  var count = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1];    /* number of codes of each length */
  var offs = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1];     /* offsets in table for each length */
  var extra = null;
  var extra_index = 0;

  var here_bits, here_op, here_val;

  /*
   Process a set of code lengths to create a canonical Huffman code.  The
   code lengths are lens[0..codes-1].  Each length corresponds to the
   symbols 0..codes-1.  The Huffman code is generated by first sorting the
   symbols by length from short to long, and retaining the symbol order
   for codes with equal lengths.  Then the code starts with all zero bits
   for the first code of the shortest length, and the codes are integer
   increments for the same length, and zeros are appended as the length
   increases.  For the deflate format, these bits are stored backwards
   from their more natural integer increment ordering, and so when the
   decoding tables are built in the large loop below, the integer codes
   are incremented backwards.

   This routine assumes, but does not check, that all of the entries in
   lens[] are in the range 0..MAXBITS.  The caller must assure this.
   1..MAXBITS is interpreted as that code length.  zero means that that
   symbol does not occur in this code.

   The codes are sorted by computing a count of codes for each length,
   creating from that a table of starting indices for each length in the
   sorted table, and then entering the symbols in order in the sorted
   table.  The sorted table is work[], with that space being provided by
   the caller.

   The length counts are used for other purposes as well, i.e. finding
   the minimum and maximum length codes, determining if there are any
   codes at all, checking for a valid set of lengths, and looking ahead
   at length counts to determine sub-table sizes when building the
   decoding tables.
   */

  /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */
  for (len = 0; len <= MAXBITS; len++) {
    count[len] = 0;
  }
  for (sym = 0; sym < codes; sym++) {
    count[lens[lens_index + sym]]++;
  }

  /* bound code lengths, force root to be within code lengths */
  root = bits;
  for (max = MAXBITS; max >= 1; max--) {
    if (count[max] !== 0) { break; }
  }
  if (root > max) {
    root = max;
  }
  if (max === 0) {                     /* no symbols to code at all */
    //table.op[opts.table_index] = 64;  //here.op = (var char)64;    /* invalid code marker */
    //table.bits[opts.table_index] = 1;   //here.bits = (var char)1;
    //table.val[opts.table_index++] = 0;   //here.val = (var short)0;
    table[table_index++] = (1 << 24) | (64 << 16) | 0;


    //table.op[opts.table_index] = 64;
    //table.bits[opts.table_index] = 1;
    //table.val[opts.table_index++] = 0;
    table[table_index++] = (1 << 24) | (64 << 16) | 0;

    opts.bits = 1;
    return 0;     /* no symbols, but wait for decoding to report error */
  }
  for (min = 1; min < max; min++) {
    if (count[min] !== 0) { break; }
  }
  if (root < min) {
    root = min;
  }

  /* check for an over-subscribed or incomplete set of lengths */
  left = 1;
  for (len = 1; len <= MAXBITS; len++) {
    left <<= 1;
    left -= count[len];
    if (left < 0) {
      return -1;
    }        /* over-subscribed */
  }
  if (left > 0 && (type === CODES || max !== 1)) {
    return -1;                      /* incomplete set */
  }

  /* generate offsets into symbol table for each length for sorting */
  offs[1] = 0;
  for (len = 1; len < MAXBITS; len++) {
    offs[len + 1] = offs[len] + count[len];
  }

  /* sort symbols by length, by symbol order within each length */
  for (sym = 0; sym < codes; sym++) {
    if (lens[lens_index + sym] !== 0) {
      work[offs[lens[lens_index + sym]]++] = sym;
    }
  }

  /*
   Create and fill in decoding tables.  In this loop, the table being
   filled is at next and has curr index bits.  The code being used is huff
   with length len.  That code is converted to an index by dropping drop
   bits off of the bottom.  For codes where len is less than drop + curr,
   those top drop + curr - len bits are incremented through all values to
   fill the table with replicated entries.

   root is the number of index bits for the root table.  When len exceeds
   root, sub-tables are created pointed to by the root entry with an index
   of the low root bits of huff.  This is saved in low to check for when a
   new sub-table should be started.  drop is zero when the root table is
   being filled, and drop is root when sub-tables are being filled.

   When a new sub-table is needed, it is necessary to look ahead in the
   code lengths to determine what size sub-table is needed.  The length
   counts are used for this, and so count[] is decremented as codes are
   entered in the tables.

   used keeps track of how many table entries have been allocated from the
   provided *table space.  It is checked for LENS and DIST tables against
   the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in
   the initial root table size constants.  See the comments in inftrees.h
   for more information.

   sym increments through all symbols, and the loop terminates when
   all codes of length max, i.e. all codes, have been processed.  This
   routine permits incomplete codes, so another loop after this one fills
   in the rest of the decoding tables with invalid code markers.
   */

  /* set up for code type */
  // poor man optimization - use if-else instead of switch,
  // to avoid deopts in old v8
  if (type === CODES) {
    base = extra = work;    /* dummy value--not used */
    end = 19;

  } else if (type === LENS) {
    base = lbase;
    base_index -= 257;
    extra = lext;
    extra_index -= 257;
    end = 256;

  } else {                    /* DISTS */
    base = dbase;
    extra = dext;
    end = -1;
  }

  /* initialize opts for loop */
  huff = 0;                   /* starting code */
  sym = 0;                    /* starting code symbol */
  len = min;                  /* starting code length */
  next = table_index;              /* current table to fill in */
  curr = root;                /* current table index bits */
  drop = 0;                   /* current bits to drop from code for index */
  low = -1;                   /* trigger new sub-table when len > root */
  used = 1 << root;          /* use root table entries */
  mask = used - 1;            /* mask for comparing low */

  /* check available table space */
  if ((type === LENS && used > ENOUGH_LENS) ||
    (type === DISTS && used > ENOUGH_DISTS)) {
    return 1;
  }

  /* process all codes and make table entries */
  for (;;) {
    /* create table entry */
    here_bits = len - drop;
    if (work[sym] < end) {
      here_op = 0;
      here_val = work[sym];
    }
    else if (work[sym] > end) {
      here_op = extra[extra_index + work[sym]];
      here_val = base[base_index + work[sym]];
    }
    else {
      here_op = 32 + 64;         /* end of block */
      here_val = 0;
    }

    /* replicate for those indices with low len bits equal to huff */
    incr = 1 << (len - drop);
    fill = 1 << curr;
    min = fill;                 /* save offset to next table */
    do {
      fill -= incr;
      table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0;
    } while (fill !== 0);

    /* backwards increment the len-bit code huff */
    incr = 1 << (len - 1);
    while (huff & incr) {
      incr >>= 1;
    }
    if (incr !== 0) {
      huff &= incr - 1;
      huff += incr;
    } else {
      huff = 0;
    }

    /* go to next symbol, update count, len */
    sym++;
    if (--count[len] === 0) {
      if (len === max) { break; }
      len = lens[lens_index + work[sym]];
    }

    /* create new sub-table if needed */
    if (len > root && (huff & mask) !== low) {
      /* if first time, transition to sub-tables */
      if (drop === 0) {
        drop = root;
      }

      /* increment past last table */
      next += min;            /* here min is 1 << curr */

      /* determine length of next table */
      curr = len - drop;
      left = 1 << curr;
      while (curr + drop < max) {
        left -= count[curr + drop];
        if (left <= 0) { break; }
        curr++;
        left <<= 1;
      }

      /* check for enough space */
      used += 1 << curr;
      if ((type === LENS && used > ENOUGH_LENS) ||
        (type === DISTS && used > ENOUGH_DISTS)) {
        return 1;
      }

      /* point entry in root table to sub-table */
      low = huff & mask;
      /*table.op[low] = curr;
      table.bits[low] = root;
      table.val[low] = next - opts.table_index;*/
      table[low] = (root << 24) | (curr << 16) | (next - table_index) |0;
    }
  }

  /* fill in remaining table entry if code is incomplete (guaranteed to have
   at most one remaining entry, since if the code is incomplete, the
   maximum code length that was allowed to get this far is one bit) */
  if (huff !== 0) {
    //table.op[next + huff] = 64;            /* invalid code marker */
    //table.bits[next + huff] = len - drop;
    //table.val[next + huff] = 0;
    table[next + huff] = ((len - drop) << 24) | (64 << 16) |0;
  }

  /* set return parameters */
  //opts.table_index += used;
  opts.bits = root;
  return 0;
};

},{"../utils/common":1}],10:[function(require,module,exports){
'use strict';

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

module.exports = {
  2:      'need dictionary',     /* Z_NEED_DICT       2  */
  1:      'stream end',          /* Z_STREAM_END      1  */
  0:      '',                    /* Z_OK              0  */
  '-1':   'file error',          /* Z_ERRNO         (-1) */
  '-2':   'stream error',        /* Z_STREAM_ERROR  (-2) */
  '-3':   'data error',          /* Z_DATA_ERROR    (-3) */
  '-4':   'insufficient memory', /* Z_MEM_ERROR     (-4) */
  '-5':   'buffer error',        /* Z_BUF_ERROR     (-5) */
  '-6':   'incompatible version' /* Z_VERSION_ERROR (-6) */
};

},{}],11:[function(require,module,exports){
'use strict';

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

function ZStream() {
  /* next input byte */
  this.input = null; // JS specific, because we have no pointers
  this.next_in = 0;
  /* number of bytes available at input */
  this.avail_in = 0;
  /* total number of input bytes read so far */
  this.total_in = 0;
  /* next output byte should be put there */
  this.output = null; // JS specific, because we have no pointers
  this.next_out = 0;
  /* remaining free space at output */
  this.avail_out = 0;
  /* total number of bytes output so far */
  this.total_out = 0;
  /* last error message, NULL if no error */
  this.msg = ''/*Z_NULL*/;
  /* not visible by applications */
  this.state = null;
  /* best guess about the data type: binary or text */
  this.data_type = 2/*Z_UNKNOWN*/;
  /* adler32 value of the uncompressed data */
  this.adler = 0;
}

module.exports = ZStream;

},{}],"/lib/inflate.js":[function(require,module,exports){
'use strict';


var zlib_inflate = require('./zlib/inflate');
var utils        = require('./utils/common');
var strings      = require('./utils/strings');
var c            = require('./zlib/constants');
var msg          = require('./zlib/messages');
var ZStream      = require('./zlib/zstream');
var GZheader     = require('./zlib/gzheader');

var toString = Object.prototype.toString;

/**
 * class Inflate
 *
 * Generic JS-style wrapper for zlib calls. If you don't need
 * streaming behaviour - use more simple functions: [[inflate]]
 * and [[inflateRaw]].
 **/

/* internal
 * inflate.chunks -> Array
 *
 * Chunks of output data, if [[Inflate#onData]] not overridden.
 **/

/**
 * Inflate.result -> Uint8Array|Array|String
 *
 * Uncompressed result, generated by default [[Inflate#onData]]
 * and [[Inflate#onEnd]] handlers. Filled after you push last chunk
 * (call [[Inflate#push]] with `Z_FINISH` / `true` param) or if you
 * push a chunk with explicit flush (call [[Inflate#push]] with
 * `Z_SYNC_FLUSH` param).
 **/

/**
 * Inflate.err -> Number
 *
 * Error code after inflate finished. 0 (Z_OK) on success.
 * Should be checked if broken data possible.
 **/

/**
 * Inflate.msg -> String
 *
 * Error message, if [[Inflate.err]] != 0
 **/


/**
 * new Inflate(options)
 * - options (Object): zlib inflate options.
 *
 * Creates new inflator instance with specified params. Throws exception
 * on bad params. Supported options:
 *
 * - `windowBits`
 * - `dictionary`
 *
 * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
 * for more information on these.
 *
 * Additional options, for internal needs:
 *
 * - `chunkSize` - size of generated data chunks (16K by default)
 * - `raw` (Boolean) - do raw inflate
 * - `to` (String) - if equal to 'string', then result will be converted
 *   from utf8 to utf16 (javascript) string. When string output requested,
 *   chunk length can differ from `chunkSize`, depending on content.
 *
 * By default, when no options set, autodetect deflate/gzip data format via
 * wrapper header.
 *
 * ##### Example:
 *
 * ```javascript
 * var pako = require('pako')
 *   , chunk1 = Uint8Array([1,2,3,4,5,6,7,8,9])
 *   , chunk2 = Uint8Array([10,11,12,13,14,15,16,17,18,19]);
 *
 * var inflate = new pako.Inflate({ level: 3});
 *
 * inflate.push(chunk1, false);
 * inflate.push(chunk2, true);  // true -> last chunk
 *
 * if (inflate.err) { throw new Error(inflate.err); }
 *
 * console.log(inflate.result);
 * ```
 **/
function Inflate(options) {
  if (!(this instanceof Inflate)) return new Inflate(options);

  this.options = utils.assign({
    chunkSize: 16384,
    windowBits: 0,
    to: ''
  }, options || {});

  var opt = this.options;

  // Force window size for `raw` data, if not set directly,
  // because we have no header for autodetect.
  if (opt.raw && (opt.windowBits >= 0) && (opt.windowBits < 16)) {
    opt.windowBits = -opt.windowBits;
    if (opt.windowBits === 0) { opt.windowBits = -15; }
  }

  // If `windowBits` not defined (and mode not raw) - set autodetect flag for gzip/deflate
  if ((opt.windowBits >= 0) && (opt.windowBits < 16) &&
      !(options && options.windowBits)) {
    opt.windowBits += 32;
  }

  // Gzip header has no info about windows size, we can do autodetect only
  // for deflate. So, if window size not set, force it to max when gzip possible
  if ((opt.windowBits > 15) && (opt.windowBits < 48)) {
    // bit 3 (16) -> gzipped data
    // bit 4 (32) -> autodetect gzip/deflate
    if ((opt.windowBits & 15) === 0) {
      opt.windowBits |= 15;
    }
  }

  this.err    = 0;      // error code, if happens (0 = Z_OK)
  this.msg    = '';     // error message
  this.ended  = false;  // used to avoid multiple onEnd() calls
  this.chunks = [];     // chunks of compressed data

  this.strm   = new ZStream();
  this.strm.avail_out = 0;

  var status  = zlib_inflate.inflateInit2(
    this.strm,
    opt.windowBits
  );

  if (status !== c.Z_OK) {
    throw new Error(msg[status]);
  }

  this.header = new GZheader();

  zlib_inflate.inflateGetHeader(this.strm, this.header);
}

/**
 * Inflate#push(data[, mode]) -> Boolean
 * - data (Uint8Array|Array|ArrayBuffer|String): input data
 * - mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes.
 *   See constants. Skipped or `false` means Z_NO_FLUSH, `true` means Z_FINISH.
 *
 * Sends input data to inflate pipe, generating [[Inflate#onData]] calls with
 * new output chunks. Returns `true` on success. The last data block must have
 * mode Z_FINISH (or `true`). That will flush internal pending buffers and call
 * [[Inflate#onEnd]]. For interim explicit flushes (without ending the stream) you
 * can use mode Z_SYNC_FLUSH, keeping the decompression context.
 *
 * On fail call [[Inflate#onEnd]] with error code and return false.
 *
 * We strongly recommend to use `Uint8Array` on input for best speed (output
 * format is detected automatically). Also, don't skip last param and always
 * use the same type in your code (boolean or number). That will improve JS speed.
 *
 * For regular `Array`-s make sure all elements are [0..255].
 *
 * ##### Example
 *
 * ```javascript
 * push(chunk, false); // push one of data chunks
 * ...
 * push(chunk, true);  // push last chunk
 * ```
 **/
Inflate.prototype.push = function (data, mode) {
  var strm = this.strm;
  var chunkSize = this.options.chunkSize;
  var dictionary = this.options.dictionary;
  var status, _mode;
  var next_out_utf8, tail, utf8str;
  var dict;

  // Flag to properly process Z_BUF_ERROR on testing inflate call
  // when we check that all output data was flushed.
  var allowBufError = false;

  if (this.ended) { return false; }
  _mode = (mode === ~~mode) ? mode : ((mode === true) ? c.Z_FINISH : c.Z_NO_FLUSH);

  // Convert data if needed
  if (typeof data === 'string') {
    // Only binary strings can be decompressed on practice
    strm.input = strings.binstring2buf(data);
  } else if (toString.call(data) === '[object ArrayBuffer]') {
    strm.input = new Uint8Array(data);
  } else {
    strm.input = data;
  }

  strm.next_in = 0;
  strm.avail_in = strm.input.length;

  do {
    if (strm.avail_out === 0) {
      strm.output = new utils.Buf8(chunkSize);
      strm.next_out = 0;
      strm.avail_out = chunkSize;
    }

    status = zlib_inflate.inflate(strm, c.Z_NO_FLUSH);    /* no bad return value */

    if (status === c.Z_NEED_DICT && dictionary) {
      // Convert data if needed
      if (typeof dictionary === 'string') {
        dict = strings.string2buf(dictionary);
      } else if (toString.call(dictionary) === '[object ArrayBuffer]') {
        dict = new Uint8Array(dictionary);
      } else {
        dict = dictionary;
      }

      status = zlib_inflate.inflateSetDictionary(this.strm, dict);

    }

    if (status === c.Z_BUF_ERROR && allowBufError === true) {
      status = c.Z_OK;
      allowBufError = false;
    }

    if (status !== c.Z_STREAM_END && status !== c.Z_OK) {
      this.onEnd(status);
      this.ended = true;
      return false;
    }

    if (strm.next_out) {
      if (strm.avail_out === 0 || status === c.Z_STREAM_END || (strm.avail_in === 0 && (_mode === c.Z_FINISH || _mode === c.Z_SYNC_FLUSH))) {

        if (this.options.to === 'string') {

          next_out_utf8 = strings.utf8border(strm.output, strm.next_out);

          tail = strm.next_out - next_out_utf8;
          utf8str = strings.buf2string(strm.output, next_out_utf8);

          // move tail
          strm.next_out = tail;
          strm.avail_out = chunkSize - tail;
          if (tail) { utils.arraySet(strm.output, strm.output, next_out_utf8, tail, 0); }

          this.onData(utf8str);

        } else {
          this.onData(utils.shrinkBuf(strm.output, strm.next_out));
        }
      }
    }

    // When no more input data, we should check that internal inflate buffers
    // are flushed. The only way to do it when avail_out = 0 - run one more
    // inflate pass. But if output data not exists, inflate return Z_BUF_ERROR.
    // Here we set flag to process this error properly.
    //
    // NOTE. Deflate does not return error in this case and does not needs such
    // logic.
    if (strm.avail_in === 0 && strm.avail_out === 0) {
      allowBufError = true;
    }

  } while ((strm.avail_in > 0 || strm.avail_out === 0) && status !== c.Z_STREAM_END);

  if (status === c.Z_STREAM_END) {
    _mode = c.Z_FINISH;
  }

  // Finalize on the last chunk.
  if (_mode === c.Z_FINISH) {
    status = zlib_inflate.inflateEnd(this.strm);
    this.onEnd(status);
    this.ended = true;
    return status === c.Z_OK;
  }

  // callback interim results if Z_SYNC_FLUSH.
  if (_mode === c.Z_SYNC_FLUSH) {
    this.onEnd(c.Z_OK);
    strm.avail_out = 0;
    return true;
  }

  return true;
};


/**
 * Inflate#onData(chunk) -> Void
 * - chunk (Uint8Array|Array|String): output data. Type of array depends
 *   on js engine support. When string output requested, each chunk
 *   will be string.
 *
 * By default, stores data blocks in `chunks[]` property and glue
 * those in `onEnd`. Override this handler, if you need another behaviour.
 **/
Inflate.prototype.onData = function (chunk) {
  this.chunks.push(chunk);
};


/**
 * Inflate#onEnd(status) -> Void
 * - status (Number): inflate status. 0 (Z_OK) on success,
 *   other if not.
 *
 * Called either after you tell inflate that the input stream is
 * complete (Z_FINISH) or should be flushed (Z_SYNC_FLUSH)
 * or if an error happened. By default - join collected chunks,
 * free memory and fill `results` / `err` properties.
 **/
Inflate.prototype.onEnd = function (status) {
  // On success - join
  if (status === c.Z_OK) {
    if (this.options.to === 'string') {
      // Glue & convert here, until we teach pako to send
      // utf8 aligned strings to onData
      this.result = this.chunks.join('');
    } else {
      this.result = utils.flattenChunks(this.chunks);
    }
  }
  this.chunks = [];
  this.err = status;
  this.msg = this.strm.msg;
};


/**
 * inflate(data[, options]) -> Uint8Array|Array|String
 * - data (Uint8Array|Array|String): input data to decompress.
 * - options (Object): zlib inflate options.
 *
 * Decompress `data` with inflate/ungzip and `options`. Autodetect
 * format via wrapper header by default. That's why we don't provide
 * separate `ungzip` method.
 *
 * Supported options are:
 *
 * - windowBits
 *
 * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
 * for more information.
 *
 * Sugar (options):
 *
 * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify
 *   negative windowBits implicitly.
 * - `to` (String) - if equal to 'string', then result will be converted
 *   from utf8 to utf16 (javascript) string. When string output requested,
 *   chunk length can differ from `chunkSize`, depending on content.
 *
 *
 * ##### Example:
 *
 * ```javascript
 * var pako = require('pako')
 *   , input = pako.deflate([1,2,3,4,5,6,7,8,9])
 *   , output;
 *
 * try {
 *   output = pako.inflate(input);
 * } catch (err)
 *   console.log(err);
 * }
 * ```
 **/
function inflate(input, options) {
  var inflator = new Inflate(options);

  inflator.push(input, true);

  // That will never happens, if you don't cheat with options :)
  if (inflator.err) { throw inflator.msg || msg[inflator.err]; }

  return inflator.result;
}


/**
 * inflateRaw(data[, options]) -> Uint8Array|Array|String
 * - data (Uint8Array|Array|String): input data to decompress.
 * - options (Object): zlib inflate options.
 *
 * The same as [[inflate]], but creates raw data, without wrapper
 * (header and adler32 crc).
 **/
function inflateRaw(input, options) {
  options = options || {};
  options.raw = true;
  return inflate(input, options);
}


/**
 * ungzip(data[, options]) -> Uint8Array|Array|String
 * - data (Uint8Array|Array|String): input data to decompress.
 * - options (Object): zlib inflate options.
 *
 * Just shortcut to [[inflate]], because it autodetects format
 * by header.content. Done for convenience.
 **/


exports.Inflate = Inflate;
exports.inflate = inflate;
exports.inflateRaw = inflateRaw;
exports.ungzip  = inflate;

},{"./utils/common":1,"./utils/strings":2,"./zlib/constants":4,"./zlib/gzheader":6,"./zlib/inflate":8,"./zlib/messages":10,"./zlib/zstream":11}]},{},[])("/lib/inflate.js")
});;
/* pako 1.0.6 nodeca/pako */(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.pako = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
'use strict';


var TYPED_OK =  (typeof Uint8Array !== 'undefined') &&
                (typeof Uint16Array !== 'undefined') &&
                (typeof Int32Array !== 'undefined');

function _has(obj, key) {
  return Object.prototype.hasOwnProperty.call(obj, key);
}

exports.assign = function (obj /*from1, from2, from3, ...*/) {
  var sources = Array.prototype.slice.call(arguments, 1);
  while (sources.length) {
    var source = sources.shift();
    if (!source) { continue; }

    if (typeof source !== 'object') {
      throw new TypeError(source + 'must be non-object');
    }

    for (var p in source) {
      if (_has(source, p)) {
        obj[p] = source[p];
      }
    }
  }

  return obj;
};


// reduce buffer size, avoiding mem copy
exports.shrinkBuf = function (buf, size) {
  if (buf.length === size) { return buf; }
  if (buf.subarray) { return buf.subarray(0, size); }
  buf.length = size;
  return buf;
};


var fnTyped = {
  arraySet: function (dest, src, src_offs, len, dest_offs) {
    if (src.subarray && dest.subarray) {
      dest.set(src.subarray(src_offs, src_offs + len), dest_offs);
      return;
    }
    // Fallback to ordinary array
    for (var i = 0; i < len; i++) {
      dest[dest_offs + i] = src[src_offs + i];
    }
  },
  // Join array of chunks to single array.
  flattenChunks: function (chunks) {
    var i, l, len, pos, chunk, result;

    // calculate data length
    len = 0;
    for (i = 0, l = chunks.length; i < l; i++) {
      len += chunks[i].length;
    }

    // join chunks
    result = new Uint8Array(len);
    pos = 0;
    for (i = 0, l = chunks.length; i < l; i++) {
      chunk = chunks[i];
      result.set(chunk, pos);
      pos += chunk.length;
    }

    return result;
  }
};

var fnUntyped = {
  arraySet: function (dest, src, src_offs, len, dest_offs) {
    for (var i = 0; i < len; i++) {
      dest[dest_offs + i] = src[src_offs + i];
    }
  },
  // Join array of chunks to single array.
  flattenChunks: function (chunks) {
    return [].concat.apply([], chunks);
  }
};


// Enable/Disable typed arrays use, for testing
//
exports.setTyped = function (on) {
  if (on) {
    exports.Buf8  = Uint8Array;
    exports.Buf16 = Uint16Array;
    exports.Buf32 = Int32Array;
    exports.assign(exports, fnTyped);
  } else {
    exports.Buf8  = Array;
    exports.Buf16 = Array;
    exports.Buf32 = Array;
    exports.assign(exports, fnUntyped);
  }
};

exports.setTyped(TYPED_OK);

},{}],2:[function(require,module,exports){
// String encode/decode helpers
'use strict';


var utils = require('./common');


// Quick check if we can use fast array to bin string conversion
//
// - apply(Array) can fail on Android 2.2
// - apply(Uint8Array) can fail on iOS 5.1 Safari
//
var STR_APPLY_OK = true;
var STR_APPLY_UIA_OK = true;

try { String.fromCharCode.apply(null, [ 0 ]); } catch (__) { STR_APPLY_OK = false; }
try { String.fromCharCode.apply(null, new Uint8Array(1)); } catch (__) { STR_APPLY_UIA_OK = false; }


// Table with utf8 lengths (calculated by first byte of sequence)
// Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS,
// because max possible codepoint is 0x10ffff
var _utf8len = new utils.Buf8(256);
for (var q = 0; q < 256; q++) {
  _utf8len[q] = (q >= 252 ? 6 : q >= 248 ? 5 : q >= 240 ? 4 : q >= 224 ? 3 : q >= 192 ? 2 : 1);
}
_utf8len[254] = _utf8len[254] = 1; // Invalid sequence start


// convert string to array (typed, when possible)
exports.string2buf = function (str) {
  var buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0;

  // count binary size
  for (m_pos = 0; m_pos < str_len; m_pos++) {
    c = str.charCodeAt(m_pos);
    if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) {
      c2 = str.charCodeAt(m_pos + 1);
      if ((c2 & 0xfc00) === 0xdc00) {
        c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00);
        m_pos++;
      }
    }
    buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4;
  }

  // allocate buffer
  buf = new utils.Buf8(buf_len);

  // convert
  for (i = 0, m_pos = 0; i < buf_len; m_pos++) {
    c = str.charCodeAt(m_pos);
    if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) {
      c2 = str.charCodeAt(m_pos + 1);
      if ((c2 & 0xfc00) === 0xdc00) {
        c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00);
        m_pos++;
      }
    }
    if (c < 0x80) {
      /* one byte */
      buf[i++] = c;
    } else if (c < 0x800) {
      /* two bytes */
      buf[i++] = 0xC0 | (c >>> 6);
      buf[i++] = 0x80 | (c & 0x3f);
    } else if (c < 0x10000) {
      /* three bytes */
      buf[i++] = 0xE0 | (c >>> 12);
      buf[i++] = 0x80 | (c >>> 6 & 0x3f);
      buf[i++] = 0x80 | (c & 0x3f);
    } else {
      /* four bytes */
      buf[i++] = 0xf0 | (c >>> 18);
      buf[i++] = 0x80 | (c >>> 12 & 0x3f);
      buf[i++] = 0x80 | (c >>> 6 & 0x3f);
      buf[i++] = 0x80 | (c & 0x3f);
    }
  }

  return buf;
};

// Helper (used in 2 places)
function buf2binstring(buf, len) {
  // use fallback for big arrays to avoid stack overflow
  if (len < 65537) {
    if ((buf.subarray && STR_APPLY_UIA_OK) || (!buf.subarray && STR_APPLY_OK)) {
      return String.fromCharCode.apply(null, utils.shrinkBuf(buf, len));
    }
  }

  var result = '';
  for (var i = 0; i < len; i++) {
    result += String.fromCharCode(buf[i]);
  }
  return result;
}


// Convert byte array to binary string
exports.buf2binstring = function (buf) {
  return buf2binstring(buf, buf.length);
};


// Convert binary string (typed, when possible)
exports.binstring2buf = function (str) {
  var buf = new utils.Buf8(str.length);
  for (var i = 0, len = buf.length; i < len; i++) {
    buf[i] = str.charCodeAt(i);
  }
  return buf;
};


// convert array to string
exports.buf2string = function (buf, max) {
  var i, out, c, c_len;
  var len = max || buf.length;

  // Reserve max possible length (2 words per char)
  // NB: by unknown reasons, Array is significantly faster for
  //     String.fromCharCode.apply than Uint16Array.
  var utf16buf = new Array(len * 2);

  for (out = 0, i = 0; i < len;) {
    c = buf[i++];
    // quick process ascii
    if (c < 0x80) { utf16buf[out++] = c; continue; }

    c_len = _utf8len[c];
    // skip 5 & 6 byte codes
    if (c_len > 4) { utf16buf[out++] = 0xfffd; i += c_len - 1; continue; }

    // apply mask on first byte
    c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07;
    // join the rest
    while (c_len > 1 && i < len) {
      c = (c << 6) | (buf[i++] & 0x3f);
      c_len--;
    }

    // terminated by end of string?
    if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; }

    if (c < 0x10000) {
      utf16buf[out++] = c;
    } else {
      c -= 0x10000;
      utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff);
      utf16buf[out++] = 0xdc00 | (c & 0x3ff);
    }
  }

  return buf2binstring(utf16buf, out);
};


// Calculate max possible position in utf8 buffer,
// that will not break sequence. If that's not possible
// - (very small limits) return max size as is.
//
// buf[] - utf8 bytes array
// max   - length limit (mandatory);
exports.utf8border = function (buf, max) {
  var pos;

  max = max || buf.length;
  if (max > buf.length) { max = buf.length; }

  // go back from last position, until start of sequence found
  pos = max - 1;
  while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; }

  // Very small and broken sequence,
  // return max, because we should return something anyway.
  if (pos < 0) { return max; }

  // If we came to start of buffer - that means buffer is too small,
  // return max too.
  if (pos === 0) { return max; }

  return (pos + _utf8len[buf[pos]] > max) ? pos : max;
};

},{"./common":1}],3:[function(require,module,exports){
'use strict';

// Note: adler32 takes 12% for level 0 and 2% for level 6.
// It isn't worth it to make additional optimizations as in original.
// Small size is preferable.

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

function adler32(adler, buf, len, pos) {
  var s1 = (adler & 0xffff) |0,
      s2 = ((adler >>> 16) & 0xffff) |0,
      n = 0;

  while (len !== 0) {
    // Set limit ~ twice less than 5552, to keep
    // s2 in 31-bits, because we force signed ints.
    // in other case %= will fail.
    n = len > 2000 ? 2000 : len;
    len -= n;

    do {
      s1 = (s1 + buf[pos++]) |0;
      s2 = (s2 + s1) |0;
    } while (--n);

    s1 %= 65521;
    s2 %= 65521;
  }

  return (s1 | (s2 << 16)) |0;
}


module.exports = adler32;

},{}],4:[function(require,module,exports){
'use strict';

// Note: we can't get significant speed boost here.
// So write code to minimize size - no pregenerated tables
// and array tools dependencies.

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

// Use ordinary array, since untyped makes no boost here
function makeTable() {
  var c, table = [];

  for (var n = 0; n < 256; n++) {
    c = n;
    for (var k = 0; k < 8; k++) {
      c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
    }
    table[n] = c;
  }

  return table;
}

// Create table on load. Just 255 signed longs. Not a problem.
var crcTable = makeTable();


function crc32(crc, buf, len, pos) {
  var t = crcTable,
      end = pos + len;

  crc ^= -1;

  for (var i = pos; i < end; i++) {
    crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF];
  }

  return (crc ^ (-1)); // >>> 0;
}


module.exports = crc32;

},{}],5:[function(require,module,exports){
'use strict';

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

var utils   = require('../utils/common');
var trees   = require('./trees');
var adler32 = require('./adler32');
var crc32   = require('./crc32');
var msg     = require('./messages');

/* Public constants ==========================================================*/
/* ===========================================================================*/


/* Allowed flush values; see deflate() and inflate() below for details */
var Z_NO_FLUSH      = 0;
var Z_PARTIAL_FLUSH = 1;
//var Z_SYNC_FLUSH    = 2;
var Z_FULL_FLUSH    = 3;
var Z_FINISH        = 4;
var Z_BLOCK         = 5;
//var Z_TREES         = 6;


/* Return codes for the compression/decompression functions. Negative values
 * are errors, positive values are used for special but normal events.
 */
var Z_OK            = 0;
var Z_STREAM_END    = 1;
//var Z_NEED_DICT     = 2;
//var Z_ERRNO         = -1;
var Z_STREAM_ERROR  = -2;
var Z_DATA_ERROR    = -3;
//var Z_MEM_ERROR     = -4;
var Z_BUF_ERROR     = -5;
//var Z_VERSION_ERROR = -6;


/* compression levels */
//var Z_NO_COMPRESSION      = 0;
//var Z_BEST_SPEED          = 1;
//var Z_BEST_COMPRESSION    = 9;
var Z_DEFAULT_COMPRESSION = -1;


var Z_FILTERED            = 1;
var Z_HUFFMAN_ONLY        = 2;
var Z_RLE                 = 3;
var Z_FIXED               = 4;
var Z_DEFAULT_STRATEGY    = 0;

/* Possible values of the data_type field (though see inflate()) */
//var Z_BINARY              = 0;
//var Z_TEXT                = 1;
//var Z_ASCII               = 1; // = Z_TEXT
var Z_UNKNOWN             = 2;


/* The deflate compression method */
var Z_DEFLATED  = 8;

/*============================================================================*/


var MAX_MEM_LEVEL = 9;
/* Maximum value for memLevel in deflateInit2 */
var MAX_WBITS = 15;
/* 32K LZ77 window */
var DEF_MEM_LEVEL = 8;


var LENGTH_CODES  = 29;
/* number of length codes, not counting the special END_BLOCK code */
var LITERALS      = 256;
/* number of literal bytes 0..255 */
var L_CODES       = LITERALS + 1 + LENGTH_CODES;
/* number of Literal or Length codes, including the END_BLOCK code */
var D_CODES       = 30;
/* number of distance codes */
var BL_CODES      = 19;
/* number of codes used to transfer the bit lengths */
var HEAP_SIZE     = 2 * L_CODES + 1;
/* maximum heap size */
var MAX_BITS  = 15;
/* All codes must not exceed MAX_BITS bits */

var MIN_MATCH = 3;
var MAX_MATCH = 258;
var MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1);

var PRESET_DICT = 0x20;

var INIT_STATE = 42;
var EXTRA_STATE = 69;
var NAME_STATE = 73;
var COMMENT_STATE = 91;
var HCRC_STATE = 103;
var BUSY_STATE = 113;
var FINISH_STATE = 666;

var BS_NEED_MORE      = 1; /* block not completed, need more input or more output */
var BS_BLOCK_DONE     = 2; /* block flush performed */
var BS_FINISH_STARTED = 3; /* finish started, need only more output at next deflate */
var BS_FINISH_DONE    = 4; /* finish done, accept no more input or output */

var OS_CODE = 0x03; // Unix :) . Don't detect, use this default.

function err(strm, errorCode) {
  strm.msg = msg[errorCode];
  return errorCode;
}

function rank(f) {
  return ((f) << 1) - ((f) > 4 ? 9 : 0);
}

function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } }


/* =========================================================================
 * Flush as much pending output as possible. All deflate() output goes
 * through this function so some applications may wish to modify it
 * to avoid allocating a large strm->output buffer and copying into it.
 * (See also read_buf()).
 */
function flush_pending(strm) {
  var s = strm.state;

  //_tr_flush_bits(s);
  var len = s.pending;
  if (len > strm.avail_out) {
    len = strm.avail_out;
  }
  if (len === 0) { return; }

  utils.arraySet(strm.output, s.pending_buf, s.pending_out, len, strm.next_out);
  strm.next_out += len;
  s.pending_out += len;
  strm.total_out += len;
  strm.avail_out -= len;
  s.pending -= len;
  if (s.pending === 0) {
    s.pending_out = 0;
  }
}


function flush_block_only(s, last) {
  trees._tr_flush_block(s, (s.block_start >= 0 ? s.block_start : -1), s.strstart - s.block_start, last);
  s.block_start = s.strstart;
  flush_pending(s.strm);
}


function put_byte(s, b) {
  s.pending_buf[s.pending++] = b;
}


/* =========================================================================
 * Put a short in the pending buffer. The 16-bit value is put in MSB order.
 * IN assertion: the stream state is correct and there is enough room in
 * pending_buf.
 */
function putShortMSB(s, b) {
//  put_byte(s, (Byte)(b >> 8));
//  put_byte(s, (Byte)(b & 0xff));
  s.pending_buf[s.pending++] = (b >>> 8) & 0xff;
  s.pending_buf[s.pending++] = b & 0xff;
}


/* ===========================================================================
 * Read a new buffer from the current input stream, update the adler32
 * and total number of bytes read.  All deflate() input goes through
 * this function so some applications may wish to modify it to avoid
 * allocating a large strm->input buffer and copying from it.
 * (See also flush_pending()).
 */
function read_buf(strm, buf, start, size) {
  var len = strm.avail_in;

  if (len > size) { len = size; }
  if (len === 0) { return 0; }

  strm.avail_in -= len;

  // zmemcpy(buf, strm->next_in, len);
  utils.arraySet(buf, strm.input, strm.next_in, len, start);
  if (strm.state.wrap === 1) {
    strm.adler = adler32(strm.adler, buf, len, start);
  }

  else if (strm.state.wrap === 2) {
    strm.adler = crc32(strm.adler, buf, len, start);
  }

  strm.next_in += len;
  strm.total_in += len;

  return len;
}


/* ===========================================================================
 * Set match_start to the longest match starting at the given string and
 * return its length. Matches shorter or equal to prev_length are discarded,
 * in which case the result is equal to prev_length and match_start is
 * garbage.
 * IN assertions: cur_match is the head of the hash chain for the current
 *   string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1
 * OUT assertion: the match length is not greater than s->lookahead.
 */
function longest_match(s, cur_match) {
  var chain_length = s.max_chain_length;      /* max hash chain length */
  var scan = s.strstart; /* current string */
  var match;                       /* matched string */
  var len;                           /* length of current match */
  var best_len = s.prev_length;              /* best match length so far */
  var nice_match = s.nice_match;             /* stop if match long enough */
  var limit = (s.strstart > (s.w_size - MIN_LOOKAHEAD)) ?
      s.strstart - (s.w_size - MIN_LOOKAHEAD) : 0/*NIL*/;

  var _win = s.window; // shortcut

  var wmask = s.w_mask;
  var prev  = s.prev;

  /* Stop when cur_match becomes <= limit. To simplify the code,
   * we prevent matches with the string of window index 0.
   */

  var strend = s.strstart + MAX_MATCH;
  var scan_end1  = _win[scan + best_len - 1];
  var scan_end   = _win[scan + best_len];

  /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16.
   * It is easy to get rid of this optimization if necessary.
   */
  // Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever");

  /* Do not waste too much time if we already have a good match: */
  if (s.prev_length >= s.good_match) {
    chain_length >>= 2;
  }
  /* Do not look for matches beyond the end of the input. This is necessary
   * to make deflate deterministic.
   */
  if (nice_match > s.lookahead) { nice_match = s.lookahead; }

  // Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead");

  do {
    // Assert(cur_match < s->strstart, "no future");
    match = cur_match;

    /* Skip to next match if the match length cannot increase
     * or if the match length is less than 2.  Note that the checks below
     * for insufficient lookahead only occur occasionally for performance
     * reasons.  Therefore uninitialized memory will be accessed, and
     * conditional jumps will be made that depend on those values.
     * However the length of the match is limited to the lookahead, so
     * the output of deflate is not affected by the uninitialized values.
     */

    if (_win[match + best_len]     !== scan_end  ||
        _win[match + best_len - 1] !== scan_end1 ||
        _win[match]                !== _win[scan] ||
        _win[++match]              !== _win[scan + 1]) {
      continue;
    }

    /* The check at best_len-1 can be removed because it will be made
     * again later. (This heuristic is not always a win.)
     * It is not necessary to compare scan[2] and match[2] since they
     * are always equal when the other bytes match, given that
     * the hash keys are equal and that HASH_BITS >= 8.
     */
    scan += 2;
    match++;
    // Assert(*scan == *match, "match[2]?");

    /* We check for insufficient lookahead only every 8th comparison;
     * the 256th check will be made at strstart+258.
     */
    do {
      /*jshint noempty:false*/
    } while (_win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
             _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
             _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
             _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
             scan < strend);

    // Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");

    len = MAX_MATCH - (strend - scan);
    scan = strend - MAX_MATCH;

    if (len > best_len) {
      s.match_start = cur_match;
      best_len = len;
      if (len >= nice_match) {
        break;
      }
      scan_end1  = _win[scan + best_len - 1];
      scan_end   = _win[scan + best_len];
    }
  } while ((cur_match = prev[cur_match & wmask]) > limit && --chain_length !== 0);

  if (best_len <= s.lookahead) {
    return best_len;
  }
  return s.lookahead;
}


/* ===========================================================================
 * Fill the window when the lookahead becomes insufficient.
 * Updates strstart and lookahead.
 *
 * IN assertion: lookahead < MIN_LOOKAHEAD
 * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD
 *    At least one byte has been read, or avail_in == 0; reads are
 *    performed for at least two bytes (required for the zip translate_eol
 *    option -- not supported here).
 */
function fill_window(s) {
  var _w_size = s.w_size;
  var p, n, m, more, str;

  //Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead");

  do {
    more = s.window_size - s.lookahead - s.strstart;

    // JS ints have 32 bit, block below not needed
    /* Deal with !@#$% 64K limit: */
    //if (sizeof(int) <= 2) {
    //    if (more == 0 && s->strstart == 0 && s->lookahead == 0) {
    //        more = wsize;
    //
    //  } else if (more == (unsigned)(-1)) {
    //        /* Very unlikely, but possible on 16 bit machine if
    //         * strstart == 0 && lookahead == 1 (input done a byte at time)
    //         */
    //        more--;
    //    }
    //}


    /* If the window is almost full and there is insufficient lookahead,
     * move the upper half to the lower one to make room in the upper half.
     */
    if (s.strstart >= _w_size + (_w_size - MIN_LOOKAHEAD)) {

      utils.arraySet(s.window, s.window, _w_size, _w_size, 0);
      s.match_start -= _w_size;
      s.strstart -= _w_size;
      /* we now have strstart >= MAX_DIST */
      s.block_start -= _w_size;

      /* Slide the hash table (could be avoided with 32 bit values
       at the expense of memory usage). We slide even when level == 0
       to keep the hash table consistent if we switch back to level > 0
       later. (Using level 0 permanently is not an optimal usage of
       zlib, so we don't care about this pathological case.)
       */

      n = s.hash_size;
      p = n;
      do {
        m = s.head[--p];
        s.head[p] = (m >= _w_size ? m - _w_size : 0);
      } while (--n);

      n = _w_size;
      p = n;
      do {
        m = s.prev[--p];
        s.prev[p] = (m >= _w_size ? m - _w_size : 0);
        /* If n is not on any hash chain, prev[n] is garbage but
         * its value will never be used.
         */
      } while (--n);

      more += _w_size;
    }
    if (s.strm.avail_in === 0) {
      break;
    }

    /* If there was no sliding:
     *    strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 &&
     *    more == window_size - lookahead - strstart
     * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1)
     * => more >= window_size - 2*WSIZE + 2
     * In the BIG_MEM or MMAP case (not yet supported),
     *   window_size == input_size + MIN_LOOKAHEAD  &&
     *   strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD.
     * Otherwise, window_size == 2*WSIZE so more >= 2.
     * If there was sliding, more >= WSIZE. So in all cases, more >= 2.
     */
    //Assert(more >= 2, "more < 2");
    n = read_buf(s.strm, s.window, s.strstart + s.lookahead, more);
    s.lookahead += n;

    /* Initialize the hash value now that we have some input: */
    if (s.lookahead + s.insert >= MIN_MATCH) {
      str = s.strstart - s.insert;
      s.ins_h = s.window[str];

      /* UPDATE_HASH(s, s->ins_h, s->window[str + 1]); */
      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + 1]) & s.hash_mask;
//#if MIN_MATCH != 3
//        Call update_hash() MIN_MATCH-3 more times
//#endif
      while (s.insert) {
        /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */
        s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask;

        s.prev[str & s.w_mask] = s.head[s.ins_h];
        s.head[s.ins_h] = str;
        str++;
        s.insert--;
        if (s.lookahead + s.insert < MIN_MATCH) {
          break;
        }
      }
    }
    /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage,
     * but this is not important since only literal bytes will be emitted.
     */

  } while (s.lookahead < MIN_LOOKAHEAD && s.strm.avail_in !== 0);

  /* If the WIN_INIT bytes after the end of the current data have never been
   * written, then zero those bytes in order to avoid memory check reports of
   * the use of uninitialized (or uninitialised as Julian writes) bytes by
   * the longest match routines.  Update the high water mark for the next
   * time through here.  WIN_INIT is set to MAX_MATCH since the longest match
   * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead.
   */
//  if (s.high_water < s.window_size) {
//    var curr = s.strstart + s.lookahead;
//    var init = 0;
//
//    if (s.high_water < curr) {
//      /* Previous high water mark below current data -- zero WIN_INIT
//       * bytes or up to end of window, whichever is less.
//       */
//      init = s.window_size - curr;
//      if (init > WIN_INIT)
//        init = WIN_INIT;
//      zmemzero(s->window + curr, (unsigned)init);
//      s->high_water = curr + init;
//    }
//    else if (s->high_water < (ulg)curr + WIN_INIT) {
//      /* High water mark at or above current data, but below current data
//       * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up
//       * to end of window, whichever is less.
//       */
//      init = (ulg)curr + WIN_INIT - s->high_water;
//      if (init > s->window_size - s->high_water)
//        init = s->window_size - s->high_water;
//      zmemzero(s->window + s->high_water, (unsigned)init);
//      s->high_water += init;
//    }
//  }
//
//  Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD,
//    "not enough room for search");
}

/* ===========================================================================
 * Copy without compression as much as possible from the input stream, return
 * the current block state.
 * This function does not insert new strings in the dictionary since
 * uncompressible data is probably not useful. This function is used
 * only for the level=0 compression option.
 * NOTE: this function should be optimized to avoid extra copying from
 * window to pending_buf.
 */
function deflate_stored(s, flush) {
  /* Stored blocks are limited to 0xffff bytes, pending_buf is limited
   * to pending_buf_size, and each stored block has a 5 byte header:
   */
  var max_block_size = 0xffff;

  if (max_block_size > s.pending_buf_size - 5) {
    max_block_size = s.pending_buf_size - 5;
  }

  /* Copy as much as possible from input to output: */
  for (;;) {
    /* Fill the window as much as possible: */
    if (s.lookahead <= 1) {

      //Assert(s->strstart < s->w_size+MAX_DIST(s) ||
      //  s->block_start >= (long)s->w_size, "slide too late");
//      if (!(s.strstart < s.w_size + (s.w_size - MIN_LOOKAHEAD) ||
//        s.block_start >= s.w_size)) {
//        throw  new Error("slide too late");
//      }

      fill_window(s);
      if (s.lookahead === 0 && flush === Z_NO_FLUSH) {
        return BS_NEED_MORE;
      }

      if (s.lookahead === 0) {
        break;
      }
      /* flush the current block */
    }
    //Assert(s->block_start >= 0L, "block gone");
//    if (s.block_start < 0) throw new Error("block gone");

    s.strstart += s.lookahead;
    s.lookahead = 0;

    /* Emit a stored block if pending_buf will be full: */
    var max_start = s.block_start + max_block_size;

    if (s.strstart === 0 || s.strstart >= max_start) {
      /* strstart == 0 is possible when wraparound on 16-bit machine */
      s.lookahead = s.strstart - max_start;
      s.strstart = max_start;
      /*** FLUSH_BLOCK(s, 0); ***/
      flush_block_only(s, false);
      if (s.strm.avail_out === 0) {
        return BS_NEED_MORE;
      }
      /***/


    }
    /* Flush if we may have to slide, otherwise block_start may become
     * negative and the data will be gone:
     */
    if (s.strstart - s.block_start >= (s.w_size - MIN_LOOKAHEAD)) {
      /*** FLUSH_BLOCK(s, 0); ***/
      flush_block_only(s, false);
      if (s.strm.avail_out === 0) {
        return BS_NEED_MORE;
      }
      /***/
    }
  }

  s.insert = 0;

  if (flush === Z_FINISH) {
    /*** FLUSH_BLOCK(s, 1); ***/
    flush_block_only(s, true);
    if (s.strm.avail_out === 0) {
      return BS_FINISH_STARTED;
    }
    /***/
    return BS_FINISH_DONE;
  }

  if (s.strstart > s.block_start) {
    /*** FLUSH_BLOCK(s, 0); ***/
    flush_block_only(s, false);
    if (s.strm.avail_out === 0) {
      return BS_NEED_MORE;
    }
    /***/
  }

  return BS_NEED_MORE;
}

/* ===========================================================================
 * Compress as much as possible from the input stream, return the current
 * block state.
 * This function does not perform lazy evaluation of matches and inserts
 * new strings in the dictionary only for unmatched strings or for short
 * matches. It is used only for the fast compression options.
 */
function deflate_fast(s, flush) {
  var hash_head;        /* head of the hash chain */
  var bflush;           /* set if current block must be flushed */

  for (;;) {
    /* Make sure that we always have enough lookahead, except
     * at the end of the input file. We need MAX_MATCH bytes
     * for the next match, plus MIN_MATCH bytes to insert the
     * string following the next match.
     */
    if (s.lookahead < MIN_LOOKAHEAD) {
      fill_window(s);
      if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) {
        return BS_NEED_MORE;
      }
      if (s.lookahead === 0) {
        break; /* flush the current block */
      }
    }

    /* Insert the string window[strstart .. strstart+2] in the
     * dictionary, and set hash_head to the head of the hash chain:
     */
    hash_head = 0/*NIL*/;
    if (s.lookahead >= MIN_MATCH) {
      /*** INSERT_STRING(s, s.strstart, hash_head); ***/
      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
      hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
      s.head[s.ins_h] = s.strstart;
      /***/
    }

    /* Find the longest match, discarding those <= prev_length.
     * At this point we have always match_length < MIN_MATCH
     */
    if (hash_head !== 0/*NIL*/ && ((s.strstart - hash_head) <= (s.w_size - MIN_LOOKAHEAD))) {
      /* To simplify the code, we prevent matches with the string
       * of window index 0 (in particular we have to avoid a match
       * of the string with itself at the start of the input file).
       */
      s.match_length = longest_match(s, hash_head);
      /* longest_match() sets match_start */
    }
    if (s.match_length >= MIN_MATCH) {
      // check_match(s, s.strstart, s.match_start, s.match_length); // for debug only

      /*** _tr_tally_dist(s, s.strstart - s.match_start,
                     s.match_length - MIN_MATCH, bflush); ***/
      bflush = trees._tr_tally(s, s.strstart - s.match_start, s.match_length - MIN_MATCH);

      s.lookahead -= s.match_length;

      /* Insert new strings in the hash table only if the match length
       * is not too large. This saves time but degrades compression.
       */
      if (s.match_length <= s.max_lazy_match/*max_insert_length*/ && s.lookahead >= MIN_MATCH) {
        s.match_length--; /* string at strstart already in table */
        do {
          s.strstart++;
          /*** INSERT_STRING(s, s.strstart, hash_head); ***/
          s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
          hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
          s.head[s.ins_h] = s.strstart;
          /***/
          /* strstart never exceeds WSIZE-MAX_MATCH, so there are
           * always MIN_MATCH bytes ahead.
           */
        } while (--s.match_length !== 0);
        s.strstart++;
      } else
      {
        s.strstart += s.match_length;
        s.match_length = 0;
        s.ins_h = s.window[s.strstart];
        /* UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); */
        s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + 1]) & s.hash_mask;

//#if MIN_MATCH != 3
//                Call UPDATE_HASH() MIN_MATCH-3 more times
//#endif
        /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not
         * matter since it will be recomputed at next deflate call.
         */
      }
    } else {
      /* No match, output a literal byte */
      //Tracevv((stderr,"%c", s.window[s.strstart]));
      /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
      bflush = trees._tr_tally(s, 0, s.window[s.strstart]);

      s.lookahead--;
      s.strstart++;
    }
    if (bflush) {
      /*** FLUSH_BLOCK(s, 0); ***/
      flush_block_only(s, false);
      if (s.strm.avail_out === 0) {
        return BS_NEED_MORE;
      }
      /***/
    }
  }
  s.insert = ((s.strstart < (MIN_MATCH - 1)) ? s.strstart : MIN_MATCH - 1);
  if (flush === Z_FINISH) {
    /*** FLUSH_BLOCK(s, 1); ***/
    flush_block_only(s, true);
    if (s.strm.avail_out === 0) {
      return BS_FINISH_STARTED;
    }
    /***/
    return BS_FINISH_DONE;
  }
  if (s.last_lit) {
    /*** FLUSH_BLOCK(s, 0); ***/
    flush_block_only(s, false);
    if (s.strm.avail_out === 0) {
      return BS_NEED_MORE;
    }
    /***/
  }
  return BS_BLOCK_DONE;
}

/* ===========================================================================
 * Same as above, but achieves better compression. We use a lazy
 * evaluation for matches: a match is finally adopted only if there is
 * no better match at the next window position.
 */
function deflate_slow(s, flush) {
  var hash_head;          /* head of hash chain */
  var bflush;              /* set if current block must be flushed */

  var max_insert;

  /* Process the input block. */
  for (;;) {
    /* Make sure that we always have enough lookahead, except
     * at the end of the input file. We need MAX_MATCH bytes
     * for the next match, plus MIN_MATCH bytes to insert the
     * string following the next match.
     */
    if (s.lookahead < MIN_LOOKAHEAD) {
      fill_window(s);
      if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) {
        return BS_NEED_MORE;
      }
      if (s.lookahead === 0) { break; } /* flush the current block */
    }

    /* Insert the string window[strstart .. strstart+2] in the
     * dictionary, and set hash_head to the head of the hash chain:
     */
    hash_head = 0/*NIL*/;
    if (s.lookahead >= MIN_MATCH) {
      /*** INSERT_STRING(s, s.strstart, hash_head); ***/
      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
      hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
      s.head[s.ins_h] = s.strstart;
      /***/
    }

    /* Find the longest match, discarding those <= prev_length.
     */
    s.prev_length = s.match_length;
    s.prev_match = s.match_start;
    s.match_length = MIN_MATCH - 1;

    if (hash_head !== 0/*NIL*/ && s.prev_length < s.max_lazy_match &&
        s.strstart - hash_head <= (s.w_size - MIN_LOOKAHEAD)/*MAX_DIST(s)*/) {
      /* To simplify the code, we prevent matches with the string
       * of window index 0 (in particular we have to avoid a match
       * of the string with itself at the start of the input file).
       */
      s.match_length = longest_match(s, hash_head);
      /* longest_match() sets match_start */

      if (s.match_length <= 5 &&
         (s.strategy === Z_FILTERED || (s.match_length === MIN_MATCH && s.strstart - s.match_start > 4096/*TOO_FAR*/))) {

        /* If prev_match is also MIN_MATCH, match_start is garbage
         * but we will ignore the current match anyway.
         */
        s.match_length = MIN_MATCH - 1;
      }
    }
    /* If there was a match at the previous step and the current
     * match is not better, output the previous match:
     */
    if (s.prev_length >= MIN_MATCH && s.match_length <= s.prev_length) {
      max_insert = s.strstart + s.lookahead - MIN_MATCH;
      /* Do not insert strings in hash table beyond this. */

      //check_match(s, s.strstart-1, s.prev_match, s.prev_length);

      /***_tr_tally_dist(s, s.strstart - 1 - s.prev_match,
                     s.prev_length - MIN_MATCH, bflush);***/
      bflush = trees._tr_tally(s, s.strstart - 1 - s.prev_match, s.prev_length - MIN_MATCH);
      /* Insert in hash table all strings up to the end of the match.
       * strstart-1 and strstart are already inserted. If there is not
       * enough lookahead, the last two strings are not inserted in
       * the hash table.
       */
      s.lookahead -= s.prev_length - 1;
      s.prev_length -= 2;
      do {
        if (++s.strstart <= max_insert) {
          /*** INSERT_STRING(s, s.strstart, hash_head); ***/
          s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
          hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
          s.head[s.ins_h] = s.strstart;
          /***/
        }
      } while (--s.prev_length !== 0);
      s.match_available = 0;
      s.match_length = MIN_MATCH - 1;
      s.strstart++;

      if (bflush) {
        /*** FLUSH_BLOCK(s, 0); ***/
        flush_block_only(s, false);
        if (s.strm.avail_out === 0) {
          return BS_NEED_MORE;
        }
        /***/
      }

    } else if (s.match_available) {
      /* If there was no match at the previous position, output a
       * single literal. If there was a match but the current match
       * is longer, truncate the previous match to a single literal.
       */
      //Tracevv((stderr,"%c", s->window[s->strstart-1]));
      /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/
      bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]);

      if (bflush) {
        /*** FLUSH_BLOCK_ONLY(s, 0) ***/
        flush_block_only(s, false);
        /***/
      }
      s.strstart++;
      s.lookahead--;
      if (s.strm.avail_out === 0) {
        return BS_NEED_MORE;
      }
    } else {
      /* There is no previous match to compare with, wait for
       * the next step to decide.
       */
      s.match_available = 1;
      s.strstart++;
      s.lookahead--;
    }
  }
  //Assert (flush != Z_NO_FLUSH, "no flush?");
  if (s.match_available) {
    //Tracevv((stderr,"%c", s->window[s->strstart-1]));
    /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/
    bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]);

    s.match_available = 0;
  }
  s.insert = s.strstart < MIN_MATCH - 1 ? s.strstart : MIN_MATCH - 1;
  if (flush === Z_FINISH) {
    /*** FLUSH_BLOCK(s, 1); ***/
    flush_block_only(s, true);
    if (s.strm.avail_out === 0) {
      return BS_FINISH_STARTED;
    }
    /***/
    return BS_FINISH_DONE;
  }
  if (s.last_lit) {
    /*** FLUSH_BLOCK(s, 0); ***/
    flush_block_only(s, false);
    if (s.strm.avail_out === 0) {
      return BS_NEED_MORE;
    }
    /***/
  }

  return BS_BLOCK_DONE;
}


/* ===========================================================================
 * For Z_RLE, simply look for runs of bytes, generate matches only of distance
 * one.  Do not maintain a hash table.  (It will be regenerated if this run of
 * deflate switches away from Z_RLE.)
 */
function deflate_rle(s, flush) {
  var bflush;            /* set if current block must be flushed */
  var prev;              /* byte at distance one to match */
  var scan, strend;      /* scan goes up to strend for length of run */

  var _win = s.window;

  for (;;) {
    /* Make sure that we always have enough lookahead, except
     * at the end of the input file. We need MAX_MATCH bytes
     * for the longest run, plus one for the unrolled loop.
     */
    if (s.lookahead <= MAX_MATCH) {
      fill_window(s);
      if (s.lookahead <= MAX_MATCH && flush === Z_NO_FLUSH) {
        return BS_NEED_MORE;
      }
      if (s.lookahead === 0) { break; } /* flush the current block */
    }

    /* See how many times the previous byte repeats */
    s.match_length = 0;
    if (s.lookahead >= MIN_MATCH && s.strstart > 0) {
      scan = s.strstart - 1;
      prev = _win[scan];
      if (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan]) {
        strend = s.strstart + MAX_MATCH;
        do {
          /*jshint noempty:false*/
        } while (prev === _win[++scan] && prev === _win[++scan] &&
                 prev === _win[++scan] && prev === _win[++scan] &&
                 prev === _win[++scan] && prev === _win[++scan] &&
                 prev === _win[++scan] && prev === _win[++scan] &&
                 scan < strend);
        s.match_length = MAX_MATCH - (strend - scan);
        if (s.match_length > s.lookahead) {
          s.match_length = s.lookahead;
        }
      }
      //Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan");
    }

    /* Emit match if have run of MIN_MATCH or longer, else emit literal */
    if (s.match_length >= MIN_MATCH) {
      //check_match(s, s.strstart, s.strstart - 1, s.match_length);

      /*** _tr_tally_dist(s, 1, s.match_length - MIN_MATCH, bflush); ***/
      bflush = trees._tr_tally(s, 1, s.match_length - MIN_MATCH);

      s.lookahead -= s.match_length;
      s.strstart += s.match_length;
      s.match_length = 0;
    } else {
      /* No match, output a literal byte */
      //Tracevv((stderr,"%c", s->window[s->strstart]));
      /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
      bflush = trees._tr_tally(s, 0, s.window[s.strstart]);

      s.lookahead--;
      s.strstart++;
    }
    if (bflush) {
      /*** FLUSH_BLOCK(s, 0); ***/
      flush_block_only(s, false);
      if (s.strm.avail_out === 0) {
        return BS_NEED_MORE;
      }
      /***/
    }
  }
  s.insert = 0;
  if (flush === Z_FINISH) {
    /*** FLUSH_BLOCK(s, 1); ***/
    flush_block_only(s, true);
    if (s.strm.avail_out === 0) {
      return BS_FINISH_STARTED;
    }
    /***/
    return BS_FINISH_DONE;
  }
  if (s.last_lit) {
    /*** FLUSH_BLOCK(s, 0); ***/
    flush_block_only(s, false);
    if (s.strm.avail_out === 0) {
      return BS_NEED_MORE;
    }
    /***/
  }
  return BS_BLOCK_DONE;
}

/* ===========================================================================
 * For Z_HUFFMAN_ONLY, do not look for matches.  Do not maintain a hash table.
 * (It will be regenerated if this run of deflate switches away from Huffman.)
 */
function deflate_huff(s, flush) {
  var bflush;             /* set if current block must be flushed */

  for (;;) {
    /* Make sure that we have a literal to write. */
    if (s.lookahead === 0) {
      fill_window(s);
      if (s.lookahead === 0) {
        if (flush === Z_NO_FLUSH) {
          return BS_NEED_MORE;
        }
        break;      /* flush the current block */
      }
    }

    /* Output a literal byte */
    s.match_length = 0;
    //Tracevv((stderr,"%c", s->window[s->strstart]));
    /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
    bflush = trees._tr_tally(s, 0, s.window[s.strstart]);
    s.lookahead--;
    s.strstart++;
    if (bflush) {
      /*** FLUSH_BLOCK(s, 0); ***/
      flush_block_only(s, false);
      if (s.strm.avail_out === 0) {
        return BS_NEED_MORE;
      }
      /***/
    }
  }
  s.insert = 0;
  if (flush === Z_FINISH) {
    /*** FLUSH_BLOCK(s, 1); ***/
    flush_block_only(s, true);
    if (s.strm.avail_out === 0) {
      return BS_FINISH_STARTED;
    }
    /***/
    return BS_FINISH_DONE;
  }
  if (s.last_lit) {
    /*** FLUSH_BLOCK(s, 0); ***/
    flush_block_only(s, false);
    if (s.strm.avail_out === 0) {
      return BS_NEED_MORE;
    }
    /***/
  }
  return BS_BLOCK_DONE;
}

/* Values for max_lazy_match, good_match and max_chain_length, depending on
 * the desired pack level (0..9). The values given below have been tuned to
 * exclude worst case performance for pathological files. Better values may be
 * found for specific files.
 */
function Config(good_length, max_lazy, nice_length, max_chain, func) {
  this.good_length = good_length;
  this.max_lazy = max_lazy;
  this.nice_length = nice_length;
  this.max_chain = max_chain;
  this.func = func;
}

var configuration_table;

configuration_table = [
  /*      good lazy nice chain */
  new Config(0, 0, 0, 0, deflate_stored),          /* 0 store only */
  new Config(4, 4, 8, 4, deflate_fast),            /* 1 max speed, no lazy matches */
  new Config(4, 5, 16, 8, deflate_fast),           /* 2 */
  new Config(4, 6, 32, 32, deflate_fast),          /* 3 */

  new Config(4, 4, 16, 16, deflate_slow),          /* 4 lazy matches */
  new Config(8, 16, 32, 32, deflate_slow),         /* 5 */
  new Config(8, 16, 128, 128, deflate_slow),       /* 6 */
  new Config(8, 32, 128, 256, deflate_slow),       /* 7 */
  new Config(32, 128, 258, 1024, deflate_slow),    /* 8 */
  new Config(32, 258, 258, 4096, deflate_slow)     /* 9 max compression */
];


/* ===========================================================================
 * Initialize the "longest match" routines for a new zlib stream
 */
function lm_init(s) {
  s.window_size = 2 * s.w_size;

  /*** CLEAR_HASH(s); ***/
  zero(s.head); // Fill with NIL (= 0);

  /* Set the default configuration parameters:
   */
  s.max_lazy_match = configuration_table[s.level].max_lazy;
  s.good_match = configuration_table[s.level].good_length;
  s.nice_match = configuration_table[s.level].nice_length;
  s.max_chain_length = configuration_table[s.level].max_chain;

  s.strstart = 0;
  s.block_start = 0;
  s.lookahead = 0;
  s.insert = 0;
  s.match_length = s.prev_length = MIN_MATCH - 1;
  s.match_available = 0;
  s.ins_h = 0;
}


function DeflateState() {
  this.strm = null;            /* pointer back to this zlib stream */
  this.status = 0;            /* as the name implies */
  this.pending_buf = null;      /* output still pending */
  this.pending_buf_size = 0;  /* size of pending_buf */
  this.pending_out = 0;       /* next pending byte to output to the stream */
  this.pending = 0;           /* nb of bytes in the pending buffer */
  this.wrap = 0;              /* bit 0 true for zlib, bit 1 true for gzip */
  this.gzhead = null;         /* gzip header information to write */
  this.gzindex = 0;           /* where in extra, name, or comment */
  this.method = Z_DEFLATED; /* can only be DEFLATED */
  this.last_flush = -1;   /* value of flush param for previous deflate call */

  this.w_size = 0;  /* LZ77 window size (32K by default) */
  this.w_bits = 0;  /* log2(w_size)  (8..16) */
  this.w_mask = 0;  /* w_size - 1 */

  this.window = null;
  /* Sliding window. Input bytes are read into the second half of the window,
   * and move to the first half later to keep a dictionary of at least wSize
   * bytes. With this organization, matches are limited to a distance of
   * wSize-MAX_MATCH bytes, but this ensures that IO is always
   * performed with a length multiple of the block size.
   */

  this.window_size = 0;
  /* Actual size of window: 2*wSize, except when the user input buffer
   * is directly used as sliding window.
   */

  this.prev = null;
  /* Link to older string with same hash index. To limit the size of this
   * array to 64K, this link is maintained only for the last 32K strings.
   * An index in this array is thus a window index modulo 32K.
   */

  this.head = null;   /* Heads of the hash chains or NIL. */

  this.ins_h = 0;       /* hash index of string to be inserted */
  this.hash_size = 0;   /* number of elements in hash table */
  this.hash_bits = 0;   /* log2(hash_size) */
  this.hash_mask = 0;   /* hash_size-1 */

  this.hash_shift = 0;
  /* Number of bits by which ins_h must be shifted at each input
   * step. It must be such that after MIN_MATCH steps, the oldest
   * byte no longer takes part in the hash key, that is:
   *   hash_shift * MIN_MATCH >= hash_bits
   */

  this.block_start = 0;
  /* Window position at the beginning of the current output block. Gets
   * negative when the window is moved backwards.
   */

  this.match_length = 0;      /* length of best match */
  this.prev_match = 0;        /* previous match */
  this.match_available = 0;   /* set if previous match exists */
  this.strstart = 0;          /* start of string to insert */
  this.match_start = 0;       /* start of matching string */
  this.lookahead = 0;         /* number of valid bytes ahead in window */

  this.prev_length = 0;
  /* Length of the best match at previous step. Matches not greater than this
   * are discarded. This is used in the lazy match evaluation.
   */

  this.max_chain_length = 0;
  /* To speed up deflation, hash chains are never searched beyond this
   * length.  A higher limit improves compression ratio but degrades the
   * speed.
   */

  this.max_lazy_match = 0;
  /* Attempt to find a better match only when the current match is strictly
   * smaller than this value. This mechanism is used only for compression
   * levels >= 4.
   */
  // That's alias to max_lazy_match, don't use directly
  //this.max_insert_length = 0;
  /* Insert new strings in the hash table only if the match length is not
   * greater than this length. This saves time but degrades compression.
   * max_insert_length is used only for compression levels <= 3.
   */

  this.level = 0;     /* compression level (1..9) */
  this.strategy = 0;  /* favor or force Huffman coding*/

  this.good_match = 0;
  /* Use a faster search when the previous match is longer than this */

  this.nice_match = 0; /* Stop searching when current match exceeds this */

              /* used by trees.c: */

  /* Didn't use ct_data typedef below to suppress compiler warning */

  // struct ct_data_s dyn_ltree[HEAP_SIZE];   /* literal and length tree */
  // struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */
  // struct ct_data_s bl_tree[2*BL_CODES+1];  /* Huffman tree for bit lengths */

  // Use flat array of DOUBLE size, with interleaved fata,
  // because JS does not support effective
  this.dyn_ltree  = new utils.Buf16(HEAP_SIZE * 2);
  this.dyn_dtree  = new utils.Buf16((2 * D_CODES + 1) * 2);
  this.bl_tree    = new utils.Buf16((2 * BL_CODES + 1) * 2);
  zero(this.dyn_ltree);
  zero(this.dyn_dtree);
  zero(this.bl_tree);

  this.l_desc   = null;         /* desc. for literal tree */
  this.d_desc   = null;         /* desc. for distance tree */
  this.bl_desc  = null;         /* desc. for bit length tree */

  //ush bl_count[MAX_BITS+1];
  this.bl_count = new utils.Buf16(MAX_BITS + 1);
  /* number of codes at each bit length for an optimal tree */

  //int heap[2*L_CODES+1];      /* heap used to build the Huffman trees */
  this.heap = new utils.Buf16(2 * L_CODES + 1);  /* heap used to build the Huffman trees */
  zero(this.heap);

  this.heap_len = 0;               /* number of elements in the heap */
  this.heap_max = 0;               /* element of largest frequency */
  /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used.
   * The same heap array is used to build all trees.
   */

  this.depth = new utils.Buf16(2 * L_CODES + 1); //uch depth[2*L_CODES+1];
  zero(this.depth);
  /* Depth of each subtree used as tie breaker for trees of equal frequency
   */

  this.l_buf = 0;          /* buffer index for literals or lengths */

  this.lit_bufsize = 0;
  /* Size of match buffer for literals/lengths.  There are 4 reasons for
   * limiting lit_bufsize to 64K:
   *   - frequencies can be kept in 16 bit counters
   *   - if compression is not successful for the first block, all input
   *     data is still in the window so we can still emit a stored block even
   *     when input comes from standard input.  (This can also be done for
   *     all blocks if lit_bufsize is not greater than 32K.)
   *   - if compression is not successful for a file smaller than 64K, we can
   *     even emit a stored file instead of a stored block (saving 5 bytes).
   *     This is applicable only for zip (not gzip or zlib).
   *   - creating new Huffman trees less frequently may not provide fast
   *     adaptation to changes in the input data statistics. (Take for
   *     example a binary file with poorly compressible code followed by
   *     a highly compressible string table.) Smaller buffer sizes give
   *     fast adaptation but have of course the overhead of transmitting
   *     trees more frequently.
   *   - I can't count above 4
   */

  this.last_lit = 0;      /* running index in l_buf */

  this.d_buf = 0;
  /* Buffer index for distances. To simplify the code, d_buf and l_buf have
   * the same number of elements. To use different lengths, an extra flag
   * array would be necessary.
   */

  this.opt_len = 0;       /* bit length of current block with optimal trees */
  this.static_len = 0;    /* bit length of current block with static trees */
  this.matches = 0;       /* number of string matches in current block */
  this.insert = 0;        /* bytes at end of window left to insert */


  this.bi_buf = 0;
  /* Output buffer. bits are inserted starting at the bottom (least
   * significant bits).
   */
  this.bi_valid = 0;
  /* Number of valid bits in bi_buf.  All bits above the last valid bit
   * are always zero.
   */

  // Used for window memory init. We safely ignore it for JS. That makes
  // sense only for pointers and memory check tools.
  //this.high_water = 0;
  /* High water mark offset in window for initialized bytes -- bytes above
   * this are set to zero in order to avoid memory check warnings when
   * longest match routines access bytes past the input.  This is then
   * updated to the new high water mark.
   */
}


function deflateResetKeep(strm) {
  var s;

  if (!strm || !strm.state) {
    return err(strm, Z_STREAM_ERROR);
  }

  strm.total_in = strm.total_out = 0;
  strm.data_type = Z_UNKNOWN;

  s = strm.state;
  s.pending = 0;
  s.pending_out = 0;

  if (s.wrap < 0) {
    s.wrap = -s.wrap;
    /* was made negative by deflate(..., Z_FINISH); */
  }
  s.status = (s.wrap ? INIT_STATE : BUSY_STATE);
  strm.adler = (s.wrap === 2) ?
    0  // crc32(0, Z_NULL, 0)
  :
    1; // adler32(0, Z_NULL, 0)
  s.last_flush = Z_NO_FLUSH;
  trees._tr_init(s);
  return Z_OK;
}


function deflateReset(strm) {
  var ret = deflateResetKeep(strm);
  if (ret === Z_OK) {
    lm_init(strm.state);
  }
  return ret;
}


function deflateSetHeader(strm, head) {
  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
  if (strm.state.wrap !== 2) { return Z_STREAM_ERROR; }
  strm.state.gzhead = head;
  return Z_OK;
}


function deflateInit2(strm, level, method, windowBits, memLevel, strategy) {
  if (!strm) { // === Z_NULL
    return Z_STREAM_ERROR;
  }
  var wrap = 1;

  if (level === Z_DEFAULT_COMPRESSION) {
    level = 6;
  }

  if (windowBits < 0) { /* suppress zlib wrapper */
    wrap = 0;
    windowBits = -windowBits;
  }

  else if (windowBits > 15) {
    wrap = 2;           /* write gzip wrapper instead */
    windowBits -= 16;
  }


  if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED ||
    windowBits < 8 || windowBits > 15 || level < 0 || level > 9 ||
    strategy < 0 || strategy > Z_FIXED) {
    return err(strm, Z_STREAM_ERROR);
  }


  if (windowBits === 8) {
    windowBits = 9;
  }
  /* until 256-byte window bug fixed */

  var s = new DeflateState();

  strm.state = s;
  s.strm = strm;

  s.wrap = wrap;
  s.gzhead = null;
  s.w_bits = windowBits;
  s.w_size = 1 << s.w_bits;
  s.w_mask = s.w_size - 1;

  s.hash_bits = memLevel + 7;
  s.hash_size = 1 << s.hash_bits;
  s.hash_mask = s.hash_size - 1;
  s.hash_shift = ~~((s.hash_bits + MIN_MATCH - 1) / MIN_MATCH);

  s.window = new utils.Buf8(s.w_size * 2);
  s.head = new utils.Buf16(s.hash_size);
  s.prev = new utils.Buf16(s.w_size);

  // Don't need mem init magic for JS.
  //s.high_water = 0;  /* nothing written to s->window yet */

  s.lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */

  s.pending_buf_size = s.lit_bufsize * 4;

  //overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2);
  //s->pending_buf = (uchf *) overlay;
  s.pending_buf = new utils.Buf8(s.pending_buf_size);

  // It is offset from `s.pending_buf` (size is `s.lit_bufsize * 2`)
  //s->d_buf = overlay + s->lit_bufsize/sizeof(ush);
  s.d_buf = 1 * s.lit_bufsize;

  //s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize;
  s.l_buf = (1 + 2) * s.lit_bufsize;

  s.level = level;
  s.strategy = strategy;
  s.method = method;

  return deflateReset(strm);
}

function deflateInit(strm, level) {
  return deflateInit2(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
}


function deflate(strm, flush) {
  var old_flush, s;
  var beg, val; // for gzip header write only

  if (!strm || !strm.state ||
    flush > Z_BLOCK || flush < 0) {
    return strm ? err(strm, Z_STREAM_ERROR) : Z_STREAM_ERROR;
  }

  s = strm.state;

  if (!strm.output ||
      (!strm.input && strm.avail_in !== 0) ||
      (s.status === FINISH_STATE && flush !== Z_FINISH)) {
    return err(strm, (strm.avail_out === 0) ? Z_BUF_ERROR : Z_STREAM_ERROR);
  }

  s.strm = strm; /* just in case */
  old_flush = s.last_flush;
  s.last_flush = flush;

  /* Write the header */
  if (s.status === INIT_STATE) {

    if (s.wrap === 2) { // GZIP header
      strm.adler = 0;  //crc32(0L, Z_NULL, 0);
      put_byte(s, 31);
      put_byte(s, 139);
      put_byte(s, 8);
      if (!s.gzhead) { // s->gzhead == Z_NULL
        put_byte(s, 0);
        put_byte(s, 0);
        put_byte(s, 0);
        put_byte(s, 0);
        put_byte(s, 0);
        put_byte(s, s.level === 9 ? 2 :
                    (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ?
                     4 : 0));
        put_byte(s, OS_CODE);
        s.status = BUSY_STATE;
      }
      else {
        put_byte(s, (s.gzhead.text ? 1 : 0) +
                    (s.gzhead.hcrc ? 2 : 0) +
                    (!s.gzhead.extra ? 0 : 4) +
                    (!s.gzhead.name ? 0 : 8) +
                    (!s.gzhead.comment ? 0 : 16)
                );
        put_byte(s, s.gzhead.time & 0xff);
        put_byte(s, (s.gzhead.time >> 8) & 0xff);
        put_byte(s, (s.gzhead.time >> 16) & 0xff);
        put_byte(s, (s.gzhead.time >> 24) & 0xff);
        put_byte(s, s.level === 9 ? 2 :
                    (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ?
                     4 : 0));
        put_byte(s, s.gzhead.os & 0xff);
        if (s.gzhead.extra && s.gzhead.extra.length) {
          put_byte(s, s.gzhead.extra.length & 0xff);
          put_byte(s, (s.gzhead.extra.length >> 8) & 0xff);
        }
        if (s.gzhead.hcrc) {
          strm.adler = crc32(strm.adler, s.pending_buf, s.pending, 0);
        }
        s.gzindex = 0;
        s.status = EXTRA_STATE;
      }
    }
    else // DEFLATE header
    {
      var header = (Z_DEFLATED + ((s.w_bits - 8) << 4)) << 8;
      var level_flags = -1;

      if (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2) {
        level_flags = 0;
      } else if (s.level < 6) {
        level_flags = 1;
      } else if (s.level === 6) {
        level_flags = 2;
      } else {
        level_flags = 3;
      }
      header |= (level_flags << 6);
      if (s.strstart !== 0) { header |= PRESET_DICT; }
      header += 31 - (header % 31);

      s.status = BUSY_STATE;
      putShortMSB(s, header);

      /* Save the adler32 of the preset dictionary: */
      if (s.strstart !== 0) {
        putShortMSB(s, strm.adler >>> 16);
        putShortMSB(s, strm.adler & 0xffff);
      }
      strm.adler = 1; // adler32(0L, Z_NULL, 0);
    }
  }

//#ifdef GZIP
  if (s.status === EXTRA_STATE) {
    if (s.gzhead.extra/* != Z_NULL*/) {
      beg = s.pending;  /* start of bytes to update crc */

      while (s.gzindex < (s.gzhead.extra.length & 0xffff)) {
        if (s.pending === s.pending_buf_size) {
          if (s.gzhead.hcrc && s.pending > beg) {
            strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
          }
          flush_pending(strm);
          beg = s.pending;
          if (s.pending === s.pending_buf_size) {
            break;
          }
        }
        put_byte(s, s.gzhead.extra[s.gzindex] & 0xff);
        s.gzindex++;
      }
      if (s.gzhead.hcrc && s.pending > beg) {
        strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
      }
      if (s.gzindex === s.gzhead.extra.length) {
        s.gzindex = 0;
        s.status = NAME_STATE;
      }
    }
    else {
      s.status = NAME_STATE;
    }
  }
  if (s.status === NAME_STATE) {
    if (s.gzhead.name/* != Z_NULL*/) {
      beg = s.pending;  /* start of bytes to update crc */
      //int val;

      do {
        if (s.pending === s.pending_buf_size) {
          if (s.gzhead.hcrc && s.pending > beg) {
            strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
          }
          flush_pending(strm);
          beg = s.pending;
          if (s.pending === s.pending_buf_size) {
            val = 1;
            break;
          }
        }
        // JS specific: little magic to add zero terminator to end of string
        if (s.gzindex < s.gzhead.name.length) {
          val = s.gzhead.name.charCodeAt(s.gzindex++) & 0xff;
        } else {
          val = 0;
        }
        put_byte(s, val);
      } while (val !== 0);

      if (s.gzhead.hcrc && s.pending > beg) {
        strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
      }
      if (val === 0) {
        s.gzindex = 0;
        s.status = COMMENT_STATE;
      }
    }
    else {
      s.status = COMMENT_STATE;
    }
  }
  if (s.status === COMMENT_STATE) {
    if (s.gzhead.comment/* != Z_NULL*/) {
      beg = s.pending;  /* start of bytes to update crc */
      //int val;

      do {
        if (s.pending === s.pending_buf_size) {
          if (s.gzhead.hcrc && s.pending > beg) {
            strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
          }
          flush_pending(strm);
          beg = s.pending;
          if (s.pending === s.pending_buf_size) {
            val = 1;
            break;
          }
        }
        // JS specific: little magic to add zero terminator to end of string
        if (s.gzindex < s.gzhead.comment.length) {
          val = s.gzhead.comment.charCodeAt(s.gzindex++) & 0xff;
        } else {
          val = 0;
        }
        put_byte(s, val);
      } while (val !== 0);

      if (s.gzhead.hcrc && s.pending > beg) {
        strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
      }
      if (val === 0) {
        s.status = HCRC_STATE;
      }
    }
    else {
      s.status = HCRC_STATE;
    }
  }
  if (s.status === HCRC_STATE) {
    if (s.gzhead.hcrc) {
      if (s.pending + 2 > s.pending_buf_size) {
        flush_pending(strm);
      }
      if (s.pending + 2 <= s.pending_buf_size) {
        put_byte(s, strm.adler & 0xff);
        put_byte(s, (strm.adler >> 8) & 0xff);
        strm.adler = 0; //crc32(0L, Z_NULL, 0);
        s.status = BUSY_STATE;
      }
    }
    else {
      s.status = BUSY_STATE;
    }
  }
//#endif

  /* Flush as much pending output as possible */
  if (s.pending !== 0) {
    flush_pending(strm);
    if (strm.avail_out === 0) {
      /* Since avail_out is 0, deflate will be called again with
       * more output space, but possibly with both pending and
       * avail_in equal to zero. There won't be anything to do,
       * but this is not an error situation so make sure we
       * return OK instead of BUF_ERROR at next call of deflate:
       */
      s.last_flush = -1;
      return Z_OK;
    }

    /* Make sure there is something to do and avoid duplicate consecutive
     * flushes. For repeated and useless calls with Z_FINISH, we keep
     * returning Z_STREAM_END instead of Z_BUF_ERROR.
     */
  } else if (strm.avail_in === 0 && rank(flush) <= rank(old_flush) &&
    flush !== Z_FINISH) {
    return err(strm, Z_BUF_ERROR);
  }

  /* User must not provide more input after the first FINISH: */
  if (s.status === FINISH_STATE && strm.avail_in !== 0) {
    return err(strm, Z_BUF_ERROR);
  }

  /* Start a new block or continue the current one.
   */
  if (strm.avail_in !== 0 || s.lookahead !== 0 ||
    (flush !== Z_NO_FLUSH && s.status !== FINISH_STATE)) {
    var bstate = (s.strategy === Z_HUFFMAN_ONLY) ? deflate_huff(s, flush) :
      (s.strategy === Z_RLE ? deflate_rle(s, flush) :
        configuration_table[s.level].func(s, flush));

    if (bstate === BS_FINISH_STARTED || bstate === BS_FINISH_DONE) {
      s.status = FINISH_STATE;
    }
    if (bstate === BS_NEED_MORE || bstate === BS_FINISH_STARTED) {
      if (strm.avail_out === 0) {
        s.last_flush = -1;
        /* avoid BUF_ERROR next call, see above */
      }
      return Z_OK;
      /* If flush != Z_NO_FLUSH && avail_out == 0, the next call
       * of deflate should use the same flush parameter to make sure
       * that the flush is complete. So we don't have to output an
       * empty block here, this will be done at next call. This also
       * ensures that for a very small output buffer, we emit at most
       * one empty block.
       */
    }
    if (bstate === BS_BLOCK_DONE) {
      if (flush === Z_PARTIAL_FLUSH) {
        trees._tr_align(s);
      }
      else if (flush !== Z_BLOCK) { /* FULL_FLUSH or SYNC_FLUSH */

        trees._tr_stored_block(s, 0, 0, false);
        /* For a full flush, this empty block will be recognized
         * as a special marker by inflate_sync().
         */
        if (flush === Z_FULL_FLUSH) {
          /*** CLEAR_HASH(s); ***/             /* forget history */
          zero(s.head); // Fill with NIL (= 0);

          if (s.lookahead === 0) {
            s.strstart = 0;
            s.block_start = 0;
            s.insert = 0;
          }
        }
      }
      flush_pending(strm);
      if (strm.avail_out === 0) {
        s.last_flush = -1; /* avoid BUF_ERROR at next call, see above */
        return Z_OK;
      }
    }
  }
  //Assert(strm->avail_out > 0, "bug2");
  //if (strm.avail_out <= 0) { throw new Error("bug2");}

  if (flush !== Z_FINISH) { return Z_OK; }
  if (s.wrap <= 0) { return Z_STREAM_END; }

  /* Write the trailer */
  if (s.wrap === 2) {
    put_byte(s, strm.adler & 0xff);
    put_byte(s, (strm.adler >> 8) & 0xff);
    put_byte(s, (strm.adler >> 16) & 0xff);
    put_byte(s, (strm.adler >> 24) & 0xff);
    put_byte(s, strm.total_in & 0xff);
    put_byte(s, (strm.total_in >> 8) & 0xff);
    put_byte(s, (strm.total_in >> 16) & 0xff);
    put_byte(s, (strm.total_in >> 24) & 0xff);
  }
  else
  {
    putShortMSB(s, strm.adler >>> 16);
    putShortMSB(s, strm.adler & 0xffff);
  }

  flush_pending(strm);
  /* If avail_out is zero, the application will call deflate again
   * to flush the rest.
   */
  if (s.wrap > 0) { s.wrap = -s.wrap; }
  /* write the trailer only once! */
  return s.pending !== 0 ? Z_OK : Z_STREAM_END;
}

function deflateEnd(strm) {
  var status;

  if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) {
    return Z_STREAM_ERROR;
  }

  status = strm.state.status;
  if (status !== INIT_STATE &&
    status !== EXTRA_STATE &&
    status !== NAME_STATE &&
    status !== COMMENT_STATE &&
    status !== HCRC_STATE &&
    status !== BUSY_STATE &&
    status !== FINISH_STATE
  ) {
    return err(strm, Z_STREAM_ERROR);
  }

  strm.state = null;

  return status === BUSY_STATE ? err(strm, Z_DATA_ERROR) : Z_OK;
}


/* =========================================================================
 * Initializes the compression dictionary from the given byte
 * sequence without producing any compressed output.
 */
function deflateSetDictionary(strm, dictionary) {
  var dictLength = dictionary.length;

  var s;
  var str, n;
  var wrap;
  var avail;
  var next;
  var input;
  var tmpDict;

  if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) {
    return Z_STREAM_ERROR;
  }

  s = strm.state;
  wrap = s.wrap;

  if (wrap === 2 || (wrap === 1 && s.status !== INIT_STATE) || s.lookahead) {
    return Z_STREAM_ERROR;
  }

  /* when using zlib wrappers, compute Adler-32 for provided dictionary */
  if (wrap === 1) {
    /* adler32(strm->adler, dictionary, dictLength); */
    strm.adler = adler32(strm.adler, dictionary, dictLength, 0);
  }

  s.wrap = 0;   /* avoid computing Adler-32 in read_buf */

  /* if dictionary would fill window, just replace the history */
  if (dictLength >= s.w_size) {
    if (wrap === 0) {            /* already empty otherwise */
      /*** CLEAR_HASH(s); ***/
      zero(s.head); // Fill with NIL (= 0);
      s.strstart = 0;
      s.block_start = 0;
      s.insert = 0;
    }
    /* use the tail */
    // dictionary = dictionary.slice(dictLength - s.w_size);
    tmpDict = new utils.Buf8(s.w_size);
    utils.arraySet(tmpDict, dictionary, dictLength - s.w_size, s.w_size, 0);
    dictionary = tmpDict;
    dictLength = s.w_size;
  }
  /* insert dictionary into window and hash */
  avail = strm.avail_in;
  next = strm.next_in;
  input = strm.input;
  strm.avail_in = dictLength;
  strm.next_in = 0;
  strm.input = dictionary;
  fill_window(s);
  while (s.lookahead >= MIN_MATCH) {
    str = s.strstart;
    n = s.lookahead - (MIN_MATCH - 1);
    do {
      /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */
      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask;

      s.prev[str & s.w_mask] = s.head[s.ins_h];

      s.head[s.ins_h] = str;
      str++;
    } while (--n);
    s.strstart = str;
    s.lookahead = MIN_MATCH - 1;
    fill_window(s);
  }
  s.strstart += s.lookahead;
  s.block_start = s.strstart;
  s.insert = s.lookahead;
  s.lookahead = 0;
  s.match_length = s.prev_length = MIN_MATCH - 1;
  s.match_available = 0;
  strm.next_in = next;
  strm.input = input;
  strm.avail_in = avail;
  s.wrap = wrap;
  return Z_OK;
}


exports.deflateInit = deflateInit;
exports.deflateInit2 = deflateInit2;
exports.deflateReset = deflateReset;
exports.deflateResetKeep = deflateResetKeep;
exports.deflateSetHeader = deflateSetHeader;
exports.deflate = deflate;
exports.deflateEnd = deflateEnd;
exports.deflateSetDictionary = deflateSetDictionary;
exports.deflateInfo = 'pako deflate (from Nodeca project)';

/* Not implemented
exports.deflateBound = deflateBound;
exports.deflateCopy = deflateCopy;
exports.deflateParams = deflateParams;
exports.deflatePending = deflatePending;
exports.deflatePrime = deflatePrime;
exports.deflateTune = deflateTune;
*/

},{"../utils/common":1,"./adler32":3,"./crc32":4,"./messages":6,"./trees":7}],6:[function(require,module,exports){
'use strict';

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

module.exports = {
  2:      'need dictionary',     /* Z_NEED_DICT       2  */
  1:      'stream end',          /* Z_STREAM_END      1  */
  0:      '',                    /* Z_OK              0  */
  '-1':   'file error',          /* Z_ERRNO         (-1) */
  '-2':   'stream error',        /* Z_STREAM_ERROR  (-2) */
  '-3':   'data error',          /* Z_DATA_ERROR    (-3) */
  '-4':   'insufficient memory', /* Z_MEM_ERROR     (-4) */
  '-5':   'buffer error',        /* Z_BUF_ERROR     (-5) */
  '-6':   'incompatible version' /* Z_VERSION_ERROR (-6) */
};

},{}],7:[function(require,module,exports){
'use strict';

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

var utils = require('../utils/common');

/* Public constants ==========================================================*/
/* ===========================================================================*/


//var Z_FILTERED          = 1;
//var Z_HUFFMAN_ONLY      = 2;
//var Z_RLE               = 3;
var Z_FIXED               = 4;
//var Z_DEFAULT_STRATEGY  = 0;

/* Possible values of the data_type field (though see inflate()) */
var Z_BINARY              = 0;
var Z_TEXT                = 1;
//var Z_ASCII             = 1; // = Z_TEXT
var Z_UNKNOWN             = 2;

/*============================================================================*/


function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } }

// From zutil.h

var STORED_BLOCK = 0;
var STATIC_TREES = 1;
var DYN_TREES    = 2;
/* The three kinds of block type */

var MIN_MATCH    = 3;
var MAX_MATCH    = 258;
/* The minimum and maximum match lengths */

// From deflate.h
/* ===========================================================================
 * Internal compression state.
 */

var LENGTH_CODES  = 29;
/* number of length codes, not counting the special END_BLOCK code */

var LITERALS      = 256;
/* number of literal bytes 0..255 */

var L_CODES       = LITERALS + 1 + LENGTH_CODES;
/* number of Literal or Length codes, including the END_BLOCK code */

var D_CODES       = 30;
/* number of distance codes */

var BL_CODES      = 19;
/* number of codes used to transfer the bit lengths */

var HEAP_SIZE     = 2 * L_CODES + 1;
/* maximum heap size */

var MAX_BITS      = 15;
/* All codes must not exceed MAX_BITS bits */

var Buf_size      = 16;
/* size of bit buffer in bi_buf */


/* ===========================================================================
 * Constants
 */

var MAX_BL_BITS = 7;
/* Bit length codes must not exceed MAX_BL_BITS bits */

var END_BLOCK   = 256;
/* end of block literal code */

var REP_3_6     = 16;
/* repeat previous bit length 3-6 times (2 bits of repeat count) */

var REPZ_3_10   = 17;
/* repeat a zero length 3-10 times  (3 bits of repeat count) */

var REPZ_11_138 = 18;
/* repeat a zero length 11-138 times  (7 bits of repeat count) */

/* eslint-disable comma-spacing,array-bracket-spacing */
var extra_lbits =   /* extra bits for each length code */
  [0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0];

var extra_dbits =   /* extra bits for each distance code */
  [0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13];

var extra_blbits =  /* extra bits for each bit length code */
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7];

var bl_order =
  [16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15];
/* eslint-enable comma-spacing,array-bracket-spacing */

/* The lengths of the bit length codes are sent in order of decreasing
 * probability, to avoid transmitting the lengths for unused bit length codes.
 */

/* ===========================================================================
 * Local data. These are initialized only once.
 */

// We pre-fill arrays with 0 to avoid uninitialized gaps

var DIST_CODE_LEN = 512; /* see definition of array dist_code below */

// !!!! Use flat array instead of structure, Freq = i*2, Len = i*2+1
var static_ltree  = new Array((L_CODES + 2) * 2);
zero(static_ltree);
/* The static literal tree. Since the bit lengths are imposed, there is no
 * need for the L_CODES extra codes used during heap construction. However
 * The codes 286 and 287 are needed to build a canonical tree (see _tr_init
 * below).
 */

var static_dtree  = new Array(D_CODES * 2);
zero(static_dtree);
/* The static distance tree. (Actually a trivial tree since all codes use
 * 5 bits.)
 */

var _dist_code    = new Array(DIST_CODE_LEN);
zero(_dist_code);
/* Distance codes. The first 256 values correspond to the distances
 * 3 .. 258, the last 256 values correspond to the top 8 bits of
 * the 15 bit distances.
 */

var _length_code  = new Array(MAX_MATCH - MIN_MATCH + 1);
zero(_length_code);
/* length code for each normalized match length (0 == MIN_MATCH) */

var base_length   = new Array(LENGTH_CODES);
zero(base_length);
/* First normalized length for each code (0 = MIN_MATCH) */

var base_dist     = new Array(D_CODES);
zero(base_dist);
/* First normalized distance for each code (0 = distance of 1) */


function StaticTreeDesc(static_tree, extra_bits, extra_base, elems, max_length) {

  this.static_tree  = static_tree;  /* static tree or NULL */
  this.extra_bits   = extra_bits;   /* extra bits for each code or NULL */
  this.extra_base   = extra_base;   /* base index for extra_bits */
  this.elems        = elems;        /* max number of elements in the tree */
  this.max_length   = max_length;   /* max bit length for the codes */

  // show if `static_tree` has data or dummy - needed for monomorphic objects
  this.has_stree    = static_tree && static_tree.length;
}


var static_l_desc;
var static_d_desc;
var static_bl_desc;


function TreeDesc(dyn_tree, stat_desc) {
  this.dyn_tree = dyn_tree;     /* the dynamic tree */
  this.max_code = 0;            /* largest code with non zero frequency */
  this.stat_desc = stat_desc;   /* the corresponding static tree */
}



function d_code(dist) {
  return dist < 256 ? _dist_code[dist] : _dist_code[256 + (dist >>> 7)];
}


/* ===========================================================================
 * Output a short LSB first on the stream.
 * IN assertion: there is enough room in pendingBuf.
 */
function put_short(s, w) {
//    put_byte(s, (uch)((w) & 0xff));
//    put_byte(s, (uch)((ush)(w) >> 8));
  s.pending_buf[s.pending++] = (w) & 0xff;
  s.pending_buf[s.pending++] = (w >>> 8) & 0xff;
}


/* ===========================================================================
 * Send a value on a given number of bits.
 * IN assertion: length <= 16 and value fits in length bits.
 */
function send_bits(s, value, length) {
  if (s.bi_valid > (Buf_size - length)) {
    s.bi_buf |= (value << s.bi_valid) & 0xffff;
    put_short(s, s.bi_buf);
    s.bi_buf = value >> (Buf_size - s.bi_valid);
    s.bi_valid += length - Buf_size;
  } else {
    s.bi_buf |= (value << s.bi_valid) & 0xffff;
    s.bi_valid += length;
  }
}


function send_code(s, c, tree) {
  send_bits(s, tree[c * 2]/*.Code*/, tree[c * 2 + 1]/*.Len*/);
}


/* ===========================================================================
 * Reverse the first len bits of a code, using straightforward code (a faster
 * method would use a table)
 * IN assertion: 1 <= len <= 15
 */
function bi_reverse(code, len) {
  var res = 0;
  do {
    res |= code & 1;
    code >>>= 1;
    res <<= 1;
  } while (--len > 0);
  return res >>> 1;
}


/* ===========================================================================
 * Flush the bit buffer, keeping at most 7 bits in it.
 */
function bi_flush(s) {
  if (s.bi_valid === 16) {
    put_short(s, s.bi_buf);
    s.bi_buf = 0;
    s.bi_valid = 0;

  } else if (s.bi_valid >= 8) {
    s.pending_buf[s.pending++] = s.bi_buf & 0xff;
    s.bi_buf >>= 8;
    s.bi_valid -= 8;
  }
}


/* ===========================================================================
 * Compute the optimal bit lengths for a tree and update the total bit length
 * for the current block.
 * IN assertion: the fields freq and dad are set, heap[heap_max] and
 *    above are the tree nodes sorted by increasing frequency.
 * OUT assertions: the field len is set to the optimal bit length, the
 *     array bl_count contains the frequencies for each bit length.
 *     The length opt_len is updated; static_len is also updated if stree is
 *     not null.
 */
function gen_bitlen(s, desc)
//    deflate_state *s;
//    tree_desc *desc;    /* the tree descriptor */
{
  var tree            = desc.dyn_tree;
  var max_code        = desc.max_code;
  var stree           = desc.stat_desc.static_tree;
  var has_stree       = desc.stat_desc.has_stree;
  var extra           = desc.stat_desc.extra_bits;
  var base            = desc.stat_desc.extra_base;
  var max_length      = desc.stat_desc.max_length;
  var h;              /* heap index */
  var n, m;           /* iterate over the tree elements */
  var bits;           /* bit length */
  var xbits;          /* extra bits */
  var f;              /* frequency */
  var overflow = 0;   /* number of elements with bit length too large */

  for (bits = 0; bits <= MAX_BITS; bits++) {
    s.bl_count[bits] = 0;
  }

  /* In a first pass, compute the optimal bit lengths (which may
   * overflow in the case of the bit length tree).
   */
  tree[s.heap[s.heap_max] * 2 + 1]/*.Len*/ = 0; /* root of the heap */

  for (h = s.heap_max + 1; h < HEAP_SIZE; h++) {
    n = s.heap[h];
    bits = tree[tree[n * 2 + 1]/*.Dad*/ * 2 + 1]/*.Len*/ + 1;
    if (bits > max_length) {
      bits = max_length;
      overflow++;
    }
    tree[n * 2 + 1]/*.Len*/ = bits;
    /* We overwrite tree[n].Dad which is no longer needed */

    if (n > max_code) { continue; } /* not a leaf node */

    s.bl_count[bits]++;
    xbits = 0;
    if (n >= base) {
      xbits = extra[n - base];
    }
    f = tree[n * 2]/*.Freq*/;
    s.opt_len += f * (bits + xbits);
    if (has_stree) {
      s.static_len += f * (stree[n * 2 + 1]/*.Len*/ + xbits);
    }
  }
  if (overflow === 0) { return; }

  // Trace((stderr,"\nbit length overflow\n"));
  /* This happens for example on obj2 and pic of the Calgary corpus */

  /* Find the first bit length which could increase: */
  do {
    bits = max_length - 1;
    while (s.bl_count[bits] === 0) { bits--; }
    s.bl_count[bits]--;      /* move one leaf down the tree */
    s.bl_count[bits + 1] += 2; /* move one overflow item as its brother */
    s.bl_count[max_length]--;
    /* The brother of the overflow item also moves one step up,
     * but this does not affect bl_count[max_length]
     */
    overflow -= 2;
  } while (overflow > 0);

  /* Now recompute all bit lengths, scanning in increasing frequency.
   * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all
   * lengths instead of fixing only the wrong ones. This idea is taken
   * from 'ar' written by Haruhiko Okumura.)
   */
  for (bits = max_length; bits !== 0; bits--) {
    n = s.bl_count[bits];
    while (n !== 0) {
      m = s.heap[--h];
      if (m > max_code) { continue; }
      if (tree[m * 2 + 1]/*.Len*/ !== bits) {
        // Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits));
        s.opt_len += (bits - tree[m * 2 + 1]/*.Len*/) * tree[m * 2]/*.Freq*/;
        tree[m * 2 + 1]/*.Len*/ = bits;
      }
      n--;
    }
  }
}


/* ===========================================================================
 * Generate the codes for a given tree and bit counts (which need not be
 * optimal).
 * IN assertion: the array bl_count contains the bit length statistics for
 * the given tree and the field len is set for all tree elements.
 * OUT assertion: the field code is set for all tree elements of non
 *     zero code length.
 */
function gen_codes(tree, max_code, bl_count)
//    ct_data *tree;             /* the tree to decorate */
//    int max_code;              /* largest code with non zero frequency */
//    ushf *bl_count;            /* number of codes at each bit length */
{
  var next_code = new Array(MAX_BITS + 1); /* next code value for each bit length */
  var code = 0;              /* running code value */
  var bits;                  /* bit index */
  var n;                     /* code index */

  /* The distribution counts are first used to generate the code values
   * without bit reversal.
   */
  for (bits = 1; bits <= MAX_BITS; bits++) {
    next_code[bits] = code = (code + bl_count[bits - 1]) << 1;
  }
  /* Check that the bit counts in bl_count are consistent. The last code
   * must be all ones.
   */
  //Assert (code + bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1,
  //        "inconsistent bit counts");
  //Tracev((stderr,"\ngen_codes: max_code %d ", max_code));

  for (n = 0;  n <= max_code; n++) {
    var len = tree[n * 2 + 1]/*.Len*/;
    if (len === 0) { continue; }
    /* Now reverse the bits */
    tree[n * 2]/*.Code*/ = bi_reverse(next_code[len]++, len);

    //Tracecv(tree != static_ltree, (stderr,"\nn %3d %c l %2d c %4x (%x) ",
    //     n, (isgraph(n) ? n : ' '), len, tree[n].Code, next_code[len]-1));
  }
}


/* ===========================================================================
 * Initialize the various 'constant' tables.
 */
function tr_static_init() {
  var n;        /* iterates over tree elements */
  var bits;     /* bit counter */
  var length;   /* length value */
  var code;     /* code value */
  var dist;     /* distance index */
  var bl_count = new Array(MAX_BITS + 1);
  /* number of codes at each bit length for an optimal tree */

  // do check in _tr_init()
  //if (static_init_done) return;

  /* For some embedded targets, global variables are not initialized: */
/*#ifdef NO_INIT_GLOBAL_POINTERS
  static_l_desc.static_tree = static_ltree;
  static_l_desc.extra_bits = extra_lbits;
  static_d_desc.static_tree = static_dtree;
  static_d_desc.extra_bits = extra_dbits;
  static_bl_desc.extra_bits = extra_blbits;
#endif*/

  /* Initialize the mapping length (0..255) -> length code (0..28) */
  length = 0;
  for (code = 0; code < LENGTH_CODES - 1; code++) {
    base_length[code] = length;
    for (n = 0; n < (1 << extra_lbits[code]); n++) {
      _length_code[length++] = code;
    }
  }
  //Assert (length == 256, "tr_static_init: length != 256");
  /* Note that the length 255 (match length 258) can be represented
   * in two different ways: code 284 + 5 bits or code 285, so we
   * overwrite length_code[255] to use the best encoding:
   */
  _length_code[length - 1] = code;

  /* Initialize the mapping dist (0..32K) -> dist code (0..29) */
  dist = 0;
  for (code = 0; code < 16; code++) {
    base_dist[code] = dist;
    for (n = 0; n < (1 << extra_dbits[code]); n++) {
      _dist_code[dist++] = code;
    }
  }
  //Assert (dist == 256, "tr_static_init: dist != 256");
  dist >>= 7; /* from now on, all distances are divided by 128 */
  for (; code < D_CODES; code++) {
    base_dist[code] = dist << 7;
    for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) {
      _dist_code[256 + dist++] = code;
    }
  }
  //Assert (dist == 256, "tr_static_init: 256+dist != 512");

  /* Construct the codes of the static literal tree */
  for (bits = 0; bits <= MAX_BITS; bits++) {
    bl_count[bits] = 0;
  }

  n = 0;
  while (n <= 143) {
    static_ltree[n * 2 + 1]/*.Len*/ = 8;
    n++;
    bl_count[8]++;
  }
  while (n <= 255) {
    static_ltree[n * 2 + 1]/*.Len*/ = 9;
    n++;
    bl_count[9]++;
  }
  while (n <= 279) {
    static_ltree[n * 2 + 1]/*.Len*/ = 7;
    n++;
    bl_count[7]++;
  }
  while (n <= 287) {
    static_ltree[n * 2 + 1]/*.Len*/ = 8;
    n++;
    bl_count[8]++;
  }
  /* Codes 286 and 287 do not exist, but we must include them in the
   * tree construction to get a canonical Huffman tree (longest code
   * all ones)
   */
  gen_codes(static_ltree, L_CODES + 1, bl_count);

  /* The static distance tree is trivial: */
  for (n = 0; n < D_CODES; n++) {
    static_dtree[n * 2 + 1]/*.Len*/ = 5;
    static_dtree[n * 2]/*.Code*/ = bi_reverse(n, 5);
  }

  // Now data ready and we can init static trees
  static_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS + 1, L_CODES, MAX_BITS);
  static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0,          D_CODES, MAX_BITS);
  static_bl_desc = new StaticTreeDesc(new Array(0), extra_blbits, 0,         BL_CODES, MAX_BL_BITS);

  //static_init_done = true;
}


/* ===========================================================================
 * Initialize a new block.
 */
function init_block(s) {
  var n; /* iterates over tree elements */

  /* Initialize the trees. */
  for (n = 0; n < L_CODES;  n++) { s.dyn_ltree[n * 2]/*.Freq*/ = 0; }
  for (n = 0; n < D_CODES;  n++) { s.dyn_dtree[n * 2]/*.Freq*/ = 0; }
  for (n = 0; n < BL_CODES; n++) { s.bl_tree[n * 2]/*.Freq*/ = 0; }

  s.dyn_ltree[END_BLOCK * 2]/*.Freq*/ = 1;
  s.opt_len = s.static_len = 0;
  s.last_lit = s.matches = 0;
}


/* ===========================================================================
 * Flush the bit buffer and align the output on a byte boundary
 */
function bi_windup(s)
{
  if (s.bi_valid > 8) {
    put_short(s, s.bi_buf);
  } else if (s.bi_valid > 0) {
    //put_byte(s, (Byte)s->bi_buf);
    s.pending_buf[s.pending++] = s.bi_buf;
  }
  s.bi_buf = 0;
  s.bi_valid = 0;
}

/* ===========================================================================
 * Copy a stored block, storing first the length and its
 * one's complement if requested.
 */
function copy_block(s, buf, len, header)
//DeflateState *s;
//charf    *buf;    /* the input data */
//unsigned len;     /* its length */
//int      header;  /* true if block header must be written */
{
  bi_windup(s);        /* align on byte boundary */

  if (header) {
    put_short(s, len);
    put_short(s, ~len);
  }
//  while (len--) {
//    put_byte(s, *buf++);
//  }
  utils.arraySet(s.pending_buf, s.window, buf, len, s.pending);
  s.pending += len;
}

/* ===========================================================================
 * Compares to subtrees, using the tree depth as tie breaker when
 * the subtrees have equal frequency. This minimizes the worst case length.
 */
function smaller(tree, n, m, depth) {
  var _n2 = n * 2;
  var _m2 = m * 2;
  return (tree[_n2]/*.Freq*/ < tree[_m2]/*.Freq*/ ||
         (tree[_n2]/*.Freq*/ === tree[_m2]/*.Freq*/ && depth[n] <= depth[m]));
}

/* ===========================================================================
 * Restore the heap property by moving down the tree starting at node k,
 * exchanging a node with the smallest of its two sons if necessary, stopping
 * when the heap property is re-established (each father smaller than its
 * two sons).
 */
function pqdownheap(s, tree, k)
//    deflate_state *s;
//    ct_data *tree;  /* the tree to restore */
//    int k;               /* node to move down */
{
  var v = s.heap[k];
  var j = k << 1;  /* left son of k */
  while (j <= s.heap_len) {
    /* Set j to the smallest of the two sons: */
    if (j < s.heap_len &&
      smaller(tree, s.heap[j + 1], s.heap[j], s.depth)) {
      j++;
    }
    /* Exit if v is smaller than both sons */
    if (smaller(tree, v, s.heap[j], s.depth)) { break; }

    /* Exchange v with the smallest son */
    s.heap[k] = s.heap[j];
    k = j;

    /* And continue down the tree, setting j to the left son of k */
    j <<= 1;
  }
  s.heap[k] = v;
}


// inlined manually
// var SMALLEST = 1;

/* ===========================================================================
 * Send the block data compressed using the given Huffman trees
 */
function compress_block(s, ltree, dtree)
//    deflate_state *s;
//    const ct_data *ltree; /* literal tree */
//    const ct_data *dtree; /* distance tree */
{
  var dist;           /* distance of matched string */
  var lc;             /* match length or unmatched char (if dist == 0) */
  var lx = 0;         /* running index in l_buf */
  var code;           /* the code to send */
  var extra;          /* number of extra bits to send */

  if (s.last_lit !== 0) {
    do {
      dist = (s.pending_buf[s.d_buf + lx * 2] << 8) | (s.pending_buf[s.d_buf + lx * 2 + 1]);
      lc = s.pending_buf[s.l_buf + lx];
      lx++;

      if (dist === 0) {
        send_code(s, lc, ltree); /* send a literal byte */
        //Tracecv(isgraph(lc), (stderr," '%c' ", lc));
      } else {
        /* Here, lc is the match length - MIN_MATCH */
        code = _length_code[lc];
        send_code(s, code + LITERALS + 1, ltree); /* send the length code */
        extra = extra_lbits[code];
        if (extra !== 0) {
          lc -= base_length[code];
          send_bits(s, lc, extra);       /* send the extra length bits */
        }
        dist--; /* dist is now the match distance - 1 */
        code = d_code(dist);
        //Assert (code < D_CODES, "bad d_code");

        send_code(s, code, dtree);       /* send the distance code */
        extra = extra_dbits[code];
        if (extra !== 0) {
          dist -= base_dist[code];
          send_bits(s, dist, extra);   /* send the extra distance bits */
        }
      } /* literal or match pair ? */

      /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */
      //Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx,
      //       "pendingBuf overflow");

    } while (lx < s.last_lit);
  }

  send_code(s, END_BLOCK, ltree);
}


/* ===========================================================================
 * Construct one Huffman tree and assigns the code bit strings and lengths.
 * Update the total bit length for the current block.
 * IN assertion: the field freq is set for all tree elements.
 * OUT assertions: the fields len and code are set to the optimal bit length
 *     and corresponding code. The length opt_len is updated; static_len is
 *     also updated if stree is not null. The field max_code is set.
 */
function build_tree(s, desc)
//    deflate_state *s;
//    tree_desc *desc; /* the tree descriptor */
{
  var tree     = desc.dyn_tree;
  var stree    = desc.stat_desc.static_tree;
  var has_stree = desc.stat_desc.has_stree;
  var elems    = desc.stat_desc.elems;
  var n, m;          /* iterate over heap elements */
  var max_code = -1; /* largest code with non zero frequency */
  var node;          /* new node being created */

  /* Construct the initial heap, with least frequent element in
   * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1].
   * heap[0] is not used.
   */
  s.heap_len = 0;
  s.heap_max = HEAP_SIZE;

  for (n = 0; n < elems; n++) {
    if (tree[n * 2]/*.Freq*/ !== 0) {
      s.heap[++s.heap_len] = max_code = n;
      s.depth[n] = 0;

    } else {
      tree[n * 2 + 1]/*.Len*/ = 0;
    }
  }

  /* The pkzip format requires that at least one distance code exists,
   * and that at least one bit should be sent even if there is only one
   * possible code. So to avoid special checks later on we force at least
   * two codes of non zero frequency.
   */
  while (s.heap_len < 2) {
    node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0);
    tree[node * 2]/*.Freq*/ = 1;
    s.depth[node] = 0;
    s.opt_len--;

    if (has_stree) {
      s.static_len -= stree[node * 2 + 1]/*.Len*/;
    }
    /* node is 0 or 1 so it does not have extra bits */
  }
  desc.max_code = max_code;

  /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree,
   * establish sub-heaps of increasing lengths:
   */
  for (n = (s.heap_len >> 1/*int /2*/); n >= 1; n--) { pqdownheap(s, tree, n); }

  /* Construct the Huffman tree by repeatedly combining the least two
   * frequent nodes.
   */
  node = elems;              /* next internal node of the tree */
  do {
    //pqremove(s, tree, n);  /* n = node of least frequency */
    /*** pqremove ***/
    n = s.heap[1/*SMALLEST*/];
    s.heap[1/*SMALLEST*/] = s.heap[s.heap_len--];
    pqdownheap(s, tree, 1/*SMALLEST*/);
    /***/

    m = s.heap[1/*SMALLEST*/]; /* m = node of next least frequency */

    s.heap[--s.heap_max] = n; /* keep the nodes sorted by frequency */
    s.heap[--s.heap_max] = m;

    /* Create a new node father of n and m */
    tree[node * 2]/*.Freq*/ = tree[n * 2]/*.Freq*/ + tree[m * 2]/*.Freq*/;
    s.depth[node] = (s.depth[n] >= s.depth[m] ? s.depth[n] : s.depth[m]) + 1;
    tree[n * 2 + 1]/*.Dad*/ = tree[m * 2 + 1]/*.Dad*/ = node;

    /* and insert the new node in the heap */
    s.heap[1/*SMALLEST*/] = node++;
    pqdownheap(s, tree, 1/*SMALLEST*/);

  } while (s.heap_len >= 2);

  s.heap[--s.heap_max] = s.heap[1/*SMALLEST*/];

  /* At this point, the fields freq and dad are set. We can now
   * generate the bit lengths.
   */
  gen_bitlen(s, desc);

  /* The field len is now set, we can generate the bit codes */
  gen_codes(tree, max_code, s.bl_count);
}


/* ===========================================================================
 * Scan a literal or distance tree to determine the frequencies of the codes
 * in the bit length tree.
 */
function scan_tree(s, tree, max_code)
//    deflate_state *s;
//    ct_data *tree;   /* the tree to be scanned */
//    int max_code;    /* and its largest code of non zero frequency */
{
  var n;                     /* iterates over all tree elements */
  var prevlen = -1;          /* last emitted length */
  var curlen;                /* length of current code */

  var nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */

  var count = 0;             /* repeat count of the current code */
  var max_count = 7;         /* max repeat count */
  var min_count = 4;         /* min repeat count */

  if (nextlen === 0) {
    max_count = 138;
    min_count = 3;
  }
  tree[(max_code + 1) * 2 + 1]/*.Len*/ = 0xffff; /* guard */

  for (n = 0; n <= max_code; n++) {
    curlen = nextlen;
    nextlen = tree[(n + 1) * 2 + 1]/*.Len*/;

    if (++count < max_count && curlen === nextlen) {
      continue;

    } else if (count < min_count) {
      s.bl_tree[curlen * 2]/*.Freq*/ += count;

    } else if (curlen !== 0) {

      if (curlen !== prevlen) { s.bl_tree[curlen * 2]/*.Freq*/++; }
      s.bl_tree[REP_3_6 * 2]/*.Freq*/++;

    } else if (count <= 10) {
      s.bl_tree[REPZ_3_10 * 2]/*.Freq*/++;

    } else {
      s.bl_tree[REPZ_11_138 * 2]/*.Freq*/++;
    }

    count = 0;
    prevlen = curlen;

    if (nextlen === 0) {
      max_count = 138;
      min_count = 3;

    } else if (curlen === nextlen) {
      max_count = 6;
      min_count = 3;

    } else {
      max_count = 7;
      min_count = 4;
    }
  }
}


/* ===========================================================================
 * Send a literal or distance tree in compressed form, using the codes in
 * bl_tree.
 */
function send_tree(s, tree, max_code)
//    deflate_state *s;
//    ct_data *tree; /* the tree to be scanned */
//    int max_code;       /* and its largest code of non zero frequency */
{
  var n;                     /* iterates over all tree elements */
  var prevlen = -1;          /* last emitted length */
  var curlen;                /* length of current code */

  var nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */

  var count = 0;             /* repeat count of the current code */
  var max_count = 7;         /* max repeat count */
  var min_count = 4;         /* min repeat count */

  /* tree[max_code+1].Len = -1; */  /* guard already set */
  if (nextlen === 0) {
    max_count = 138;
    min_count = 3;
  }

  for (n = 0; n <= max_code; n++) {
    curlen = nextlen;
    nextlen = tree[(n + 1) * 2 + 1]/*.Len*/;

    if (++count < max_count && curlen === nextlen) {
      continue;

    } else if (count < min_count) {
      do { send_code(s, curlen, s.bl_tree); } while (--count !== 0);

    } else if (curlen !== 0) {
      if (curlen !== prevlen) {
        send_code(s, curlen, s.bl_tree);
        count--;
      }
      //Assert(count >= 3 && count <= 6, " 3_6?");
      send_code(s, REP_3_6, s.bl_tree);
      send_bits(s, count - 3, 2);

    } else if (count <= 10) {
      send_code(s, REPZ_3_10, s.bl_tree);
      send_bits(s, count - 3, 3);

    } else {
      send_code(s, REPZ_11_138, s.bl_tree);
      send_bits(s, count - 11, 7);
    }

    count = 0;
    prevlen = curlen;
    if (nextlen === 0) {
      max_count = 138;
      min_count = 3;

    } else if (curlen === nextlen) {
      max_count = 6;
      min_count = 3;

    } else {
      max_count = 7;
      min_count = 4;
    }
  }
}


/* ===========================================================================
 * Construct the Huffman tree for the bit lengths and return the index in
 * bl_order of the last bit length code to send.
 */
function build_bl_tree(s) {
  var max_blindex;  /* index of last bit length code of non zero freq */

  /* Determine the bit length frequencies for literal and distance trees */
  scan_tree(s, s.dyn_ltree, s.l_desc.max_code);
  scan_tree(s, s.dyn_dtree, s.d_desc.max_code);

  /* Build the bit length tree: */
  build_tree(s, s.bl_desc);
  /* opt_len now includes the length of the tree representations, except
   * the lengths of the bit lengths codes and the 5+5+4 bits for the counts.
   */

  /* Determine the number of bit length codes to send. The pkzip format
   * requires that at least 4 bit length codes be sent. (appnote.txt says
   * 3 but the actual value used is 4.)
   */
  for (max_blindex = BL_CODES - 1; max_blindex >= 3; max_blindex--) {
    if (s.bl_tree[bl_order[max_blindex] * 2 + 1]/*.Len*/ !== 0) {
      break;
    }
  }
  /* Update opt_len to include the bit length tree and counts */
  s.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4;
  //Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld",
  //        s->opt_len, s->static_len));

  return max_blindex;
}


/* ===========================================================================
 * Send the header for a block using dynamic Huffman trees: the counts, the
 * lengths of the bit length codes, the literal tree and the distance tree.
 * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4.
 */
function send_all_trees(s, lcodes, dcodes, blcodes)
//    deflate_state *s;
//    int lcodes, dcodes, blcodes; /* number of codes for each tree */
{
  var rank;                    /* index in bl_order */

  //Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes");
  //Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES,
  //        "too many codes");
  //Tracev((stderr, "\nbl counts: "));
  send_bits(s, lcodes - 257, 5); /* not +255 as stated in appnote.txt */
  send_bits(s, dcodes - 1,   5);
  send_bits(s, blcodes - 4,  4); /* not -3 as stated in appnote.txt */
  for (rank = 0; rank < blcodes; rank++) {
    //Tracev((stderr, "\nbl code %2d ", bl_order[rank]));
    send_bits(s, s.bl_tree[bl_order[rank] * 2 + 1]/*.Len*/, 3);
  }
  //Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent));

  send_tree(s, s.dyn_ltree, lcodes - 1); /* literal tree */
  //Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent));

  send_tree(s, s.dyn_dtree, dcodes - 1); /* distance tree */
  //Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent));
}


/* ===========================================================================
 * Check if the data type is TEXT or BINARY, using the following algorithm:
 * - TEXT if the two conditions below are satisfied:
 *    a) There are no non-portable control characters belonging to the
 *       "black list" (0..6, 14..25, 28..31).
 *    b) There is at least one printable character belonging to the
 *       "white list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255).
 * - BINARY otherwise.
 * - The following partially-portable control characters form a
 *   "gray list" that is ignored in this detection algorithm:
 *   (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}).
 * IN assertion: the fields Freq of dyn_ltree are set.
 */
function detect_data_type(s) {
  /* black_mask is the bit mask of black-listed bytes
   * set bits 0..6, 14..25, and 28..31
   * 0xf3ffc07f = binary 11110011111111111100000001111111
   */
  var black_mask = 0xf3ffc07f;
  var n;

  /* Check for non-textual ("black-listed") bytes. */
  for (n = 0; n <= 31; n++, black_mask >>>= 1) {
    if ((black_mask & 1) && (s.dyn_ltree[n * 2]/*.Freq*/ !== 0)) {
      return Z_BINARY;
    }
  }

  /* Check for textual ("white-listed") bytes. */
  if (s.dyn_ltree[9 * 2]/*.Freq*/ !== 0 || s.dyn_ltree[10 * 2]/*.Freq*/ !== 0 ||
      s.dyn_ltree[13 * 2]/*.Freq*/ !== 0) {
    return Z_TEXT;
  }
  for (n = 32; n < LITERALS; n++) {
    if (s.dyn_ltree[n * 2]/*.Freq*/ !== 0) {
      return Z_TEXT;
    }
  }

  /* There are no "black-listed" or "white-listed" bytes:
   * this stream either is empty or has tolerated ("gray-listed") bytes only.
   */
  return Z_BINARY;
}


var static_init_done = false;

/* ===========================================================================
 * Initialize the tree data structures for a new zlib stream.
 */
function _tr_init(s)
{

  if (!static_init_done) {
    tr_static_init();
    static_init_done = true;
  }

  s.l_desc  = new TreeDesc(s.dyn_ltree, static_l_desc);
  s.d_desc  = new TreeDesc(s.dyn_dtree, static_d_desc);
  s.bl_desc = new TreeDesc(s.bl_tree, static_bl_desc);

  s.bi_buf = 0;
  s.bi_valid = 0;

  /* Initialize the first block of the first file: */
  init_block(s);
}


/* ===========================================================================
 * Send a stored block
 */
function _tr_stored_block(s, buf, stored_len, last)
//DeflateState *s;
//charf *buf;       /* input block */
//ulg stored_len;   /* length of input block */
//int last;         /* one if this is the last block for a file */
{
  send_bits(s, (STORED_BLOCK << 1) + (last ? 1 : 0), 3);    /* send block type */
  copy_block(s, buf, stored_len, true); /* with header */
}


/* ===========================================================================
 * Send one empty static block to give enough lookahead for inflate.
 * This takes 10 bits, of which 7 may remain in the bit buffer.
 */
function _tr_align(s) {
  send_bits(s, STATIC_TREES << 1, 3);
  send_code(s, END_BLOCK, static_ltree);
  bi_flush(s);
}


/* ===========================================================================
 * Determine the best encoding for the current block: dynamic trees, static
 * trees or store, and output the encoded block to the zip file.
 */
function _tr_flush_block(s, buf, stored_len, last)
//DeflateState *s;
//charf *buf;       /* input block, or NULL if too old */
//ulg stored_len;   /* length of input block */
//int last;         /* one if this is the last block for a file */
{
  var opt_lenb, static_lenb;  /* opt_len and static_len in bytes */
  var max_blindex = 0;        /* index of last bit length code of non zero freq */

  /* Build the Huffman trees unless a stored block is forced */
  if (s.level > 0) {

    /* Check if the file is binary or text */
    if (s.strm.data_type === Z_UNKNOWN) {
      s.strm.data_type = detect_data_type(s);
    }

    /* Construct the literal and distance trees */
    build_tree(s, s.l_desc);
    // Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len,
    //        s->static_len));

    build_tree(s, s.d_desc);
    // Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len,
    //        s->static_len));
    /* At this point, opt_len and static_len are the total bit lengths of
     * the compressed block data, excluding the tree representations.
     */

    /* Build the bit length tree for the above two trees, and get the index
     * in bl_order of the last bit length code to send.
     */
    max_blindex = build_bl_tree(s);

    /* Determine the best encoding. Compute the block lengths in bytes. */
    opt_lenb = (s.opt_len + 3 + 7) >>> 3;
    static_lenb = (s.static_len + 3 + 7) >>> 3;

    // Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ",
    //        opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len,
    //        s->last_lit));

    if (static_lenb <= opt_lenb) { opt_lenb = static_lenb; }

  } else {
    // Assert(buf != (char*)0, "lost buf");
    opt_lenb = static_lenb = stored_len + 5; /* force a stored block */
  }

  if ((stored_len + 4 <= opt_lenb) && (buf !== -1)) {
    /* 4: two words for the lengths */

    /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE.
     * Otherwise we can't have processed more than WSIZE input bytes since
     * the last block flush, because compression would have been
     * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to
     * transform a block into a stored block.
     */
    _tr_stored_block(s, buf, stored_len, last);

  } else if (s.strategy === Z_FIXED || static_lenb === opt_lenb) {

    send_bits(s, (STATIC_TREES << 1) + (last ? 1 : 0), 3);
    compress_block(s, static_ltree, static_dtree);

  } else {
    send_bits(s, (DYN_TREES << 1) + (last ? 1 : 0), 3);
    send_all_trees(s, s.l_desc.max_code + 1, s.d_desc.max_code + 1, max_blindex + 1);
    compress_block(s, s.dyn_ltree, s.dyn_dtree);
  }
  // Assert (s->compressed_len == s->bits_sent, "bad compressed size");
  /* The above check is made mod 2^32, for files larger than 512 MB
   * and uLong implemented on 32 bits.
   */
  init_block(s);

  if (last) {
    bi_windup(s);
  }
  // Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3,
  //       s->compressed_len-7*last));
}

/* ===========================================================================
 * Save the match info and tally the frequency counts. Return true if
 * the current block must be flushed.
 */
function _tr_tally(s, dist, lc)
//    deflate_state *s;
//    unsigned dist;  /* distance of matched string */
//    unsigned lc;    /* match length-MIN_MATCH or unmatched char (if dist==0) */
{
  //var out_length, in_length, dcode;

  s.pending_buf[s.d_buf + s.last_lit * 2]     = (dist >>> 8) & 0xff;
  s.pending_buf[s.d_buf + s.last_lit * 2 + 1] = dist & 0xff;

  s.pending_buf[s.l_buf + s.last_lit] = lc & 0xff;
  s.last_lit++;

  if (dist === 0) {
    /* lc is the unmatched char */
    s.dyn_ltree[lc * 2]/*.Freq*/++;
  } else {
    s.matches++;
    /* Here, lc is the match length - MIN_MATCH */
    dist--;             /* dist = match distance - 1 */
    //Assert((ush)dist < (ush)MAX_DIST(s) &&
    //       (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) &&
    //       (ush)d_code(dist) < (ush)D_CODES,  "_tr_tally: bad match");

    s.dyn_ltree[(_length_code[lc] + LITERALS + 1) * 2]/*.Freq*/++;
    s.dyn_dtree[d_code(dist) * 2]/*.Freq*/++;
  }

// (!) This block is disabled in zlib defaults,
// don't enable it for binary compatibility

//#ifdef TRUNCATE_BLOCK
//  /* Try to guess if it is profitable to stop the current block here */
//  if ((s.last_lit & 0x1fff) === 0 && s.level > 2) {
//    /* Compute an upper bound for the compressed length */
//    out_length = s.last_lit*8;
//    in_length = s.strstart - s.block_start;
//
//    for (dcode = 0; dcode < D_CODES; dcode++) {
//      out_length += s.dyn_dtree[dcode*2]/*.Freq*/ * (5 + extra_dbits[dcode]);
//    }
//    out_length >>>= 3;
//    //Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ",
//    //       s->last_lit, in_length, out_length,
//    //       100L - out_length*100L/in_length));
//    if (s.matches < (s.last_lit>>1)/*int /2*/ && out_length < (in_length>>1)/*int /2*/) {
//      return true;
//    }
//  }
//#endif

  return (s.last_lit === s.lit_bufsize - 1);
  /* We avoid equality with lit_bufsize because of wraparound at 64K
   * on 16 bit machines and because stored blocks are restricted to
   * 64K-1 bytes.
   */
}

exports._tr_init  = _tr_init;
exports._tr_stored_block = _tr_stored_block;
exports._tr_flush_block  = _tr_flush_block;
exports._tr_tally = _tr_tally;
exports._tr_align = _tr_align;

},{"../utils/common":1}],8:[function(require,module,exports){
'use strict';

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

function ZStream() {
  /* next input byte */
  this.input = null; // JS specific, because we have no pointers
  this.next_in = 0;
  /* number of bytes available at input */
  this.avail_in = 0;
  /* total number of input bytes read so far */
  this.total_in = 0;
  /* next output byte should be put there */
  this.output = null; // JS specific, because we have no pointers
  this.next_out = 0;
  /* remaining free space at output */
  this.avail_out = 0;
  /* total number of bytes output so far */
  this.total_out = 0;
  /* last error message, NULL if no error */
  this.msg = ''/*Z_NULL*/;
  /* not visible by applications */
  this.state = null;
  /* best guess about the data type: binary or text */
  this.data_type = 2/*Z_UNKNOWN*/;
  /* adler32 value of the uncompressed data */
  this.adler = 0;
}

module.exports = ZStream;

},{}],"/lib/deflate.js":[function(require,module,exports){
'use strict';


var zlib_deflate = require('./zlib/deflate');
var utils        = require('./utils/common');
var strings      = require('./utils/strings');
var msg          = require('./zlib/messages');
var ZStream      = require('./zlib/zstream');

var toString = Object.prototype.toString;

/* Public constants ==========================================================*/
/* ===========================================================================*/

var Z_NO_FLUSH      = 0;
var Z_FINISH        = 4;

var Z_OK            = 0;
var Z_STREAM_END    = 1;
var Z_SYNC_FLUSH    = 2;

var Z_DEFAULT_COMPRESSION = -1;

var Z_DEFAULT_STRATEGY    = 0;

var Z_DEFLATED  = 8;

/* ===========================================================================*/


/**
 * class Deflate
 *
 * Generic JS-style wrapper for zlib calls. If you don't need
 * streaming behaviour - use more simple functions: [[deflate]],
 * [[deflateRaw]] and [[gzip]].
 **/

/* internal
 * Deflate.chunks -> Array
 *
 * Chunks of output data, if [[Deflate#onData]] not overridden.
 **/

/**
 * Deflate.result -> Uint8Array|Array
 *
 * Compressed result, generated by default [[Deflate#onData]]
 * and [[Deflate#onEnd]] handlers. Filled after you push last chunk
 * (call [[Deflate#push]] with `Z_FINISH` / `true` param)  or if you
 * push a chunk with explicit flush (call [[Deflate#push]] with
 * `Z_SYNC_FLUSH` param).
 **/

/**
 * Deflate.err -> Number
 *
 * Error code after deflate finished. 0 (Z_OK) on success.
 * You will not need it in real life, because deflate errors
 * are possible only on wrong options or bad `onData` / `onEnd`
 * custom handlers.
 **/

/**
 * Deflate.msg -> String
 *
 * Error message, if [[Deflate.err]] != 0
 **/


/**
 * new Deflate(options)
 * - options (Object): zlib deflate options.
 *
 * Creates new deflator instance with specified params. Throws exception
 * on bad params. Supported options:
 *
 * - `level`
 * - `windowBits`
 * - `memLevel`
 * - `strategy`
 * - `dictionary`
 *
 * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
 * for more information on these.
 *
 * Additional options, for internal needs:
 *
 * - `chunkSize` - size of generated data chunks (16K by default)
 * - `raw` (Boolean) - do raw deflate
 * - `gzip` (Boolean) - create gzip wrapper
 * - `to` (String) - if equal to 'string', then result will be "binary string"
 *    (each char code [0..255])
 * - `header` (Object) - custom header for gzip
 *   - `text` (Boolean) - true if compressed data believed to be text
 *   - `time` (Number) - modification time, unix timestamp
 *   - `os` (Number) - operation system code
 *   - `extra` (Array) - array of bytes with extra data (max 65536)
 *   - `name` (String) - file name (binary string)
 *   - `comment` (String) - comment (binary string)
 *   - `hcrc` (Boolean) - true if header crc should be added
 *
 * ##### Example:
 *
 * ```javascript
 * var pako = require('pako')
 *   , chunk1 = Uint8Array([1,2,3,4,5,6,7,8,9])
 *   , chunk2 = Uint8Array([10,11,12,13,14,15,16,17,18,19]);
 *
 * var deflate = new pako.Deflate({ level: 3});
 *
 * deflate.push(chunk1, false);
 * deflate.push(chunk2, true);  // true -> last chunk
 *
 * if (deflate.err) { throw new Error(deflate.err); }
 *
 * console.log(deflate.result);
 * ```
 **/
function Deflate(options) {
  if (!(this instanceof Deflate)) return new Deflate(options);

  this.options = utils.assign({
    level: Z_DEFAULT_COMPRESSION,
    method: Z_DEFLATED,
    chunkSize: 16384,
    windowBits: 15,
    memLevel: 8,
    strategy: Z_DEFAULT_STRATEGY,
    to: ''
  }, options || {});

  var opt = this.options;

  if (opt.raw && (opt.windowBits > 0)) {
    opt.windowBits = -opt.windowBits;
  }

  else if (opt.gzip && (opt.windowBits > 0) && (opt.windowBits < 16)) {
    opt.windowBits += 16;
  }

  this.err    = 0;      // error code, if happens (0 = Z_OK)
  this.msg    = '';     // error message
  this.ended  = false;  // used to avoid multiple onEnd() calls
  this.chunks = [];     // chunks of compressed data

  this.strm = new ZStream();
  this.strm.avail_out = 0;

  var status = zlib_deflate.deflateInit2(
    this.strm,
    opt.level,
    opt.method,
    opt.windowBits,
    opt.memLevel,
    opt.strategy
  );

  if (status !== Z_OK) {
    throw new Error(msg[status]);
  }

  if (opt.header) {
    zlib_deflate.deflateSetHeader(this.strm, opt.header);
  }

  if (opt.dictionary) {
    var dict;
    // Convert data if needed
    if (typeof opt.dictionary === 'string') {
      // If we need to compress text, change encoding to utf8.
      dict = strings.string2buf(opt.dictionary);
    } else if (toString.call(opt.dictionary) === '[object ArrayBuffer]') {
      dict = new Uint8Array(opt.dictionary);
    } else {
      dict = opt.dictionary;
    }

    status = zlib_deflate.deflateSetDictionary(this.strm, dict);

    if (status !== Z_OK) {
      throw new Error(msg[status]);
    }

    this._dict_set = true;
  }
}

/**
 * Deflate#push(data[, mode]) -> Boolean
 * - data (Uint8Array|Array|ArrayBuffer|String): input data. Strings will be
 *   converted to utf8 byte sequence.
 * - mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes.
 *   See constants. Skipped or `false` means Z_NO_FLUSH, `true` means Z_FINISH.
 *
 * Sends input data to deflate pipe, generating [[Deflate#onData]] calls with
 * new compressed chunks. Returns `true` on success. The last data block must have
 * mode Z_FINISH (or `true`). That will flush internal pending buffers and call
 * [[Deflate#onEnd]]. For interim explicit flushes (without ending the stream) you
 * can use mode Z_SYNC_FLUSH, keeping the compression context.
 *
 * On fail call [[Deflate#onEnd]] with error code and return false.
 *
 * We strongly recommend to use `Uint8Array` on input for best speed (output
 * array format is detected automatically). Also, don't skip last param and always
 * use the same type in your code (boolean or number). That will improve JS speed.
 *
 * For regular `Array`-s make sure all elements are [0..255].
 *
 * ##### Example
 *
 * ```javascript
 * push(chunk, false); // push one of data chunks
 * ...
 * push(chunk, true);  // push last chunk
 * ```
 **/
Deflate.prototype.push = function (data, mode) {
  var strm = this.strm;
  var chunkSize = this.options.chunkSize;
  var status, _mode;

  if (this.ended) { return false; }

  _mode = (mode === ~~mode) ? mode : ((mode === true) ? Z_FINISH : Z_NO_FLUSH);

  // Convert data if needed
  if (typeof data === 'string') {
    // If we need to compress text, change encoding to utf8.
    strm.input = strings.string2buf(data);
  } else if (toString.call(data) === '[object ArrayBuffer]') {
    strm.input = new Uint8Array(data);
  } else {
    strm.input = data;
  }

  strm.next_in = 0;
  strm.avail_in = strm.input.length;

  do {
    if (strm.avail_out === 0) {
      strm.output = new utils.Buf8(chunkSize);
      strm.next_out = 0;
      strm.avail_out = chunkSize;
    }
    status = zlib_deflate.deflate(strm, _mode);    /* no bad return value */

    if (status !== Z_STREAM_END && status !== Z_OK) {
      this.onEnd(status);
      this.ended = true;
      return false;
    }
    if (strm.avail_out === 0 || (strm.avail_in === 0 && (_mode === Z_FINISH || _mode === Z_SYNC_FLUSH))) {
      if (this.options.to === 'string') {
        this.onData(strings.buf2binstring(utils.shrinkBuf(strm.output, strm.next_out)));
      } else {
        this.onData(utils.shrinkBuf(strm.output, strm.next_out));
      }
    }
  } while ((strm.avail_in > 0 || strm.avail_out === 0) && status !== Z_STREAM_END);

  // Finalize on the last chunk.
  if (_mode === Z_FINISH) {
    status = zlib_deflate.deflateEnd(this.strm);
    this.onEnd(status);
    this.ended = true;
    return status === Z_OK;
  }

  // callback interim results if Z_SYNC_FLUSH.
  if (_mode === Z_SYNC_FLUSH) {
    this.onEnd(Z_OK);
    strm.avail_out = 0;
    return true;
  }

  return true;
};


/**
 * Deflate#onData(chunk) -> Void
 * - chunk (Uint8Array|Array|String): output data. Type of array depends
 *   on js engine support. When string output requested, each chunk
 *   will be string.
 *
 * By default, stores data blocks in `chunks[]` property and glue
 * those in `onEnd`. Override this handler, if you need another behaviour.
 **/
Deflate.prototype.onData = function (chunk) {
  this.chunks.push(chunk);
};


/**
 * Deflate#onEnd(status) -> Void
 * - status (Number): deflate status. 0 (Z_OK) on success,
 *   other if not.
 *
 * Called once after you tell deflate that the input stream is
 * complete (Z_FINISH) or should be flushed (Z_SYNC_FLUSH)
 * or if an error happened. By default - join collected chunks,
 * free memory and fill `results` / `err` properties.
 **/
Deflate.prototype.onEnd = function (status) {
  // On success - join
  if (status === Z_OK) {
    if (this.options.to === 'string') {
      this.result = this.chunks.join('');
    } else {
      this.result = utils.flattenChunks(this.chunks);
    }
  }
  this.chunks = [];
  this.err = status;
  this.msg = this.strm.msg;
};


/**
 * deflate(data[, options]) -> Uint8Array|Array|String
 * - data (Uint8Array|Array|String): input data to compress.
 * - options (Object): zlib deflate options.
 *
 * Compress `data` with deflate algorithm and `options`.
 *
 * Supported options are:
 *
 * - level
 * - windowBits
 * - memLevel
 * - strategy
 * - dictionary
 *
 * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
 * for more information on these.
 *
 * Sugar (options):
 *
 * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify
 *   negative windowBits implicitly.
 * - `to` (String) - if equal to 'string', then result will be "binary string"
 *    (each char code [0..255])
 *
 * ##### Example:
 *
 * ```javascript
 * var pako = require('pako')
 *   , data = Uint8Array([1,2,3,4,5,6,7,8,9]);
 *
 * console.log(pako.deflate(data));
 * ```
 **/
function deflate(input, options) {
  var deflator = new Deflate(options);

  deflator.push(input, true);

  // That will never happens, if you don't cheat with options :)
  if (deflator.err) { throw deflator.msg || msg[deflator.err]; }

  return deflator.result;
}


/**
 * deflateRaw(data[, options]) -> Uint8Array|Array|String
 * - data (Uint8Array|Array|String): input data to compress.
 * - options (Object): zlib deflate options.
 *
 * The same as [[deflate]], but creates raw data, without wrapper
 * (header and adler32 crc).
 **/
function deflateRaw(input, options) {
  options = options || {};
  options.raw = true;
  return deflate(input, options);
}


/**
 * gzip(data[, options]) -> Uint8Array|Array|String
 * - data (Uint8Array|Array|String): input data to compress.
 * - options (Object): zlib deflate options.
 *
 * The same as [[deflate]], but create gzip wrapper instead of
 * deflate one.
 **/
function gzip(input, options) {
  options = options || {};
  options.gzip = true;
  return deflate(input, options);
}


exports.Deflate = Deflate;
exports.deflate = deflate;
exports.deflateRaw = deflateRaw;
exports.gzip = gzip;

},{"./utils/common":1,"./utils/strings":2,"./zlib/deflate":5,"./zlib/messages":6,"./zlib/zstream":8}]},{},[])("/lib/deflate.js")
});;
/* pako 1.0.6 nodeca/pako */(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.pako = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
'use strict';


var zlib_deflate = require('./zlib/deflate');
var utils        = require('./utils/common');
var strings      = require('./utils/strings');
var msg          = require('./zlib/messages');
var ZStream      = require('./zlib/zstream');

var toString = Object.prototype.toString;

/* Public constants ==========================================================*/
/* ===========================================================================*/

var Z_NO_FLUSH      = 0;
var Z_FINISH        = 4;

var Z_OK            = 0;
var Z_STREAM_END    = 1;
var Z_SYNC_FLUSH    = 2;

var Z_DEFAULT_COMPRESSION = -1;

var Z_DEFAULT_STRATEGY    = 0;

var Z_DEFLATED  = 8;

/* ===========================================================================*/


/**
 * class Deflate
 *
 * Generic JS-style wrapper for zlib calls. If you don't need
 * streaming behaviour - use more simple functions: [[deflate]],
 * [[deflateRaw]] and [[gzip]].
 **/

/* internal
 * Deflate.chunks -> Array
 *
 * Chunks of output data, if [[Deflate#onData]] not overridden.
 **/

/**
 * Deflate.result -> Uint8Array|Array
 *
 * Compressed result, generated by default [[Deflate#onData]]
 * and [[Deflate#onEnd]] handlers. Filled after you push last chunk
 * (call [[Deflate#push]] with `Z_FINISH` / `true` param)  or if you
 * push a chunk with explicit flush (call [[Deflate#push]] with
 * `Z_SYNC_FLUSH` param).
 **/

/**
 * Deflate.err -> Number
 *
 * Error code after deflate finished. 0 (Z_OK) on success.
 * You will not need it in real life, because deflate errors
 * are possible only on wrong options or bad `onData` / `onEnd`
 * custom handlers.
 **/

/**
 * Deflate.msg -> String
 *
 * Error message, if [[Deflate.err]] != 0
 **/


/**
 * new Deflate(options)
 * - options (Object): zlib deflate options.
 *
 * Creates new deflator instance with specified params. Throws exception
 * on bad params. Supported options:
 *
 * - `level`
 * - `windowBits`
 * - `memLevel`
 * - `strategy`
 * - `dictionary`
 *
 * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
 * for more information on these.
 *
 * Additional options, for internal needs:
 *
 * - `chunkSize` - size of generated data chunks (16K by default)
 * - `raw` (Boolean) - do raw deflate
 * - `gzip` (Boolean) - create gzip wrapper
 * - `to` (String) - if equal to 'string', then result will be "binary string"
 *    (each char code [0..255])
 * - `header` (Object) - custom header for gzip
 *   - `text` (Boolean) - true if compressed data believed to be text
 *   - `time` (Number) - modification time, unix timestamp
 *   - `os` (Number) - operation system code
 *   - `extra` (Array) - array of bytes with extra data (max 65536)
 *   - `name` (String) - file name (binary string)
 *   - `comment` (String) - comment (binary string)
 *   - `hcrc` (Boolean) - true if header crc should be added
 *
 * ##### Example:
 *
 * ```javascript
 * var pako = require('pako')
 *   , chunk1 = Uint8Array([1,2,3,4,5,6,7,8,9])
 *   , chunk2 = Uint8Array([10,11,12,13,14,15,16,17,18,19]);
 *
 * var deflate = new pako.Deflate({ level: 3});
 *
 * deflate.push(chunk1, false);
 * deflate.push(chunk2, true);  // true -> last chunk
 *
 * if (deflate.err) { throw new Error(deflate.err); }
 *
 * console.log(deflate.result);
 * ```
 **/
function Deflate(options) {
  if (!(this instanceof Deflate)) return new Deflate(options);

  this.options = utils.assign({
    level: Z_DEFAULT_COMPRESSION,
    method: Z_DEFLATED,
    chunkSize: 16384,
    windowBits: 15,
    memLevel: 8,
    strategy: Z_DEFAULT_STRATEGY,
    to: ''
  }, options || {});

  var opt = this.options;

  if (opt.raw && (opt.windowBits > 0)) {
    opt.windowBits = -opt.windowBits;
  }

  else if (opt.gzip && (opt.windowBits > 0) && (opt.windowBits < 16)) {
    opt.windowBits += 16;
  }

  this.err    = 0;      // error code, if happens (0 = Z_OK)
  this.msg    = '';     // error message
  this.ended  = false;  // used to avoid multiple onEnd() calls
  this.chunks = [];     // chunks of compressed data

  this.strm = new ZStream();
  this.strm.avail_out = 0;

  var status = zlib_deflate.deflateInit2(
    this.strm,
    opt.level,
    opt.method,
    opt.windowBits,
    opt.memLevel,
    opt.strategy
  );

  if (status !== Z_OK) {
    throw new Error(msg[status]);
  }

  if (opt.header) {
    zlib_deflate.deflateSetHeader(this.strm, opt.header);
  }

  if (opt.dictionary) {
    var dict;
    // Convert data if needed
    if (typeof opt.dictionary === 'string') {
      // If we need to compress text, change encoding to utf8.
      dict = strings.string2buf(opt.dictionary);
    } else if (toString.call(opt.dictionary) === '[object ArrayBuffer]') {
      dict = new Uint8Array(opt.dictionary);
    } else {
      dict = opt.dictionary;
    }

    status = zlib_deflate.deflateSetDictionary(this.strm, dict);

    if (status !== Z_OK) {
      throw new Error(msg[status]);
    }

    this._dict_set = true;
  }
}

/**
 * Deflate#push(data[, mode]) -> Boolean
 * - data (Uint8Array|Array|ArrayBuffer|String): input data. Strings will be
 *   converted to utf8 byte sequence.
 * - mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes.
 *   See constants. Skipped or `false` means Z_NO_FLUSH, `true` means Z_FINISH.
 *
 * Sends input data to deflate pipe, generating [[Deflate#onData]] calls with
 * new compressed chunks. Returns `true` on success. The last data block must have
 * mode Z_FINISH (or `true`). That will flush internal pending buffers and call
 * [[Deflate#onEnd]]. For interim explicit flushes (without ending the stream) you
 * can use mode Z_SYNC_FLUSH, keeping the compression context.
 *
 * On fail call [[Deflate#onEnd]] with error code and return false.
 *
 * We strongly recommend to use `Uint8Array` on input for best speed (output
 * array format is detected automatically). Also, don't skip last param and always
 * use the same type in your code (boolean or number). That will improve JS speed.
 *
 * For regular `Array`-s make sure all elements are [0..255].
 *
 * ##### Example
 *
 * ```javascript
 * push(chunk, false); // push one of data chunks
 * ...
 * push(chunk, true);  // push last chunk
 * ```
 **/
Deflate.prototype.push = function (data, mode) {
  var strm = this.strm;
  var chunkSize = this.options.chunkSize;
  var status, _mode;

  if (this.ended) { return false; }

  _mode = (mode === ~~mode) ? mode : ((mode === true) ? Z_FINISH : Z_NO_FLUSH);

  // Convert data if needed
  if (typeof data === 'string') {
    // If we need to compress text, change encoding to utf8.
    strm.input = strings.string2buf(data);
  } else if (toString.call(data) === '[object ArrayBuffer]') {
    strm.input = new Uint8Array(data);
  } else {
    strm.input = data;
  }

  strm.next_in = 0;
  strm.avail_in = strm.input.length;

  do {
    if (strm.avail_out === 0) {
      strm.output = new utils.Buf8(chunkSize);
      strm.next_out = 0;
      strm.avail_out = chunkSize;
    }
    status = zlib_deflate.deflate(strm, _mode);    /* no bad return value */

    if (status !== Z_STREAM_END && status !== Z_OK) {
      this.onEnd(status);
      this.ended = true;
      return false;
    }
    if (strm.avail_out === 0 || (strm.avail_in === 0 && (_mode === Z_FINISH || _mode === Z_SYNC_FLUSH))) {
      if (this.options.to === 'string') {
        this.onData(strings.buf2binstring(utils.shrinkBuf(strm.output, strm.next_out)));
      } else {
        this.onData(utils.shrinkBuf(strm.output, strm.next_out));
      }
    }
  } while ((strm.avail_in > 0 || strm.avail_out === 0) && status !== Z_STREAM_END);

  // Finalize on the last chunk.
  if (_mode === Z_FINISH) {
    status = zlib_deflate.deflateEnd(this.strm);
    this.onEnd(status);
    this.ended = true;
    return status === Z_OK;
  }

  // callback interim results if Z_SYNC_FLUSH.
  if (_mode === Z_SYNC_FLUSH) {
    this.onEnd(Z_OK);
    strm.avail_out = 0;
    return true;
  }

  return true;
};


/**
 * Deflate#onData(chunk) -> Void
 * - chunk (Uint8Array|Array|String): output data. Type of array depends
 *   on js engine support. When string output requested, each chunk
 *   will be string.
 *
 * By default, stores data blocks in `chunks[]` property and glue
 * those in `onEnd`. Override this handler, if you need another behaviour.
 **/
Deflate.prototype.onData = function (chunk) {
  this.chunks.push(chunk);
};


/**
 * Deflate#onEnd(status) -> Void
 * - status (Number): deflate status. 0 (Z_OK) on success,
 *   other if not.
 *
 * Called once after you tell deflate that the input stream is
 * complete (Z_FINISH) or should be flushed (Z_SYNC_FLUSH)
 * or if an error happened. By default - join collected chunks,
 * free memory and fill `results` / `err` properties.
 **/
Deflate.prototype.onEnd = function (status) {
  // On success - join
  if (status === Z_OK) {
    if (this.options.to === 'string') {
      this.result = this.chunks.join('');
    } else {
      this.result = utils.flattenChunks(this.chunks);
    }
  }
  this.chunks = [];
  this.err = status;
  this.msg = this.strm.msg;
};


/**
 * deflate(data[, options]) -> Uint8Array|Array|String
 * - data (Uint8Array|Array|String): input data to compress.
 * - options (Object): zlib deflate options.
 *
 * Compress `data` with deflate algorithm and `options`.
 *
 * Supported options are:
 *
 * - level
 * - windowBits
 * - memLevel
 * - strategy
 * - dictionary
 *
 * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
 * for more information on these.
 *
 * Sugar (options):
 *
 * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify
 *   negative windowBits implicitly.
 * - `to` (String) - if equal to 'string', then result will be "binary string"
 *    (each char code [0..255])
 *
 * ##### Example:
 *
 * ```javascript
 * var pako = require('pako')
 *   , data = Uint8Array([1,2,3,4,5,6,7,8,9]);
 *
 * console.log(pako.deflate(data));
 * ```
 **/
function deflate(input, options) {
  var deflator = new Deflate(options);

  deflator.push(input, true);

  // That will never happens, if you don't cheat with options :)
  if (deflator.err) { throw deflator.msg || msg[deflator.err]; }

  return deflator.result;
}


/**
 * deflateRaw(data[, options]) -> Uint8Array|Array|String
 * - data (Uint8Array|Array|String): input data to compress.
 * - options (Object): zlib deflate options.
 *
 * The same as [[deflate]], but creates raw data, without wrapper
 * (header and adler32 crc).
 **/
function deflateRaw(input, options) {
  options = options || {};
  options.raw = true;
  return deflate(input, options);
}


/**
 * gzip(data[, options]) -> Uint8Array|Array|String
 * - data (Uint8Array|Array|String): input data to compress.
 * - options (Object): zlib deflate options.
 *
 * The same as [[deflate]], but create gzip wrapper instead of
 * deflate one.
 **/
function gzip(input, options) {
  options = options || {};
  options.gzip = true;
  return deflate(input, options);
}


exports.Deflate = Deflate;
exports.deflate = deflate;
exports.deflateRaw = deflateRaw;
exports.gzip = gzip;

},{"./utils/common":3,"./utils/strings":4,"./zlib/deflate":8,"./zlib/messages":13,"./zlib/zstream":15}],2:[function(require,module,exports){
'use strict';


var zlib_inflate = require('./zlib/inflate');
var utils        = require('./utils/common');
var strings      = require('./utils/strings');
var c            = require('./zlib/constants');
var msg          = require('./zlib/messages');
var ZStream      = require('./zlib/zstream');
var GZheader     = require('./zlib/gzheader');

var toString = Object.prototype.toString;

/**
 * class Inflate
 *
 * Generic JS-style wrapper for zlib calls. If you don't need
 * streaming behaviour - use more simple functions: [[inflate]]
 * and [[inflateRaw]].
 **/

/* internal
 * inflate.chunks -> Array
 *
 * Chunks of output data, if [[Inflate#onData]] not overridden.
 **/

/**
 * Inflate.result -> Uint8Array|Array|String
 *
 * Uncompressed result, generated by default [[Inflate#onData]]
 * and [[Inflate#onEnd]] handlers. Filled after you push last chunk
 * (call [[Inflate#push]] with `Z_FINISH` / `true` param) or if you
 * push a chunk with explicit flush (call [[Inflate#push]] with
 * `Z_SYNC_FLUSH` param).
 **/

/**
 * Inflate.err -> Number
 *
 * Error code after inflate finished. 0 (Z_OK) on success.
 * Should be checked if broken data possible.
 **/

/**
 * Inflate.msg -> String
 *
 * Error message, if [[Inflate.err]] != 0
 **/


/**
 * new Inflate(options)
 * - options (Object): zlib inflate options.
 *
 * Creates new inflator instance with specified params. Throws exception
 * on bad params. Supported options:
 *
 * - `windowBits`
 * - `dictionary`
 *
 * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
 * for more information on these.
 *
 * Additional options, for internal needs:
 *
 * - `chunkSize` - size of generated data chunks (16K by default)
 * - `raw` (Boolean) - do raw inflate
 * - `to` (String) - if equal to 'string', then result will be converted
 *   from utf8 to utf16 (javascript) string. When string output requested,
 *   chunk length can differ from `chunkSize`, depending on content.
 *
 * By default, when no options set, autodetect deflate/gzip data format via
 * wrapper header.
 *
 * ##### Example:
 *
 * ```javascript
 * var pako = require('pako')
 *   , chunk1 = Uint8Array([1,2,3,4,5,6,7,8,9])
 *   , chunk2 = Uint8Array([10,11,12,13,14,15,16,17,18,19]);
 *
 * var inflate = new pako.Inflate({ level: 3});
 *
 * inflate.push(chunk1, false);
 * inflate.push(chunk2, true);  // true -> last chunk
 *
 * if (inflate.err) { throw new Error(inflate.err); }
 *
 * console.log(inflate.result);
 * ```
 **/
function Inflate(options) {
  if (!(this instanceof Inflate)) return new Inflate(options);

  this.options = utils.assign({
    chunkSize: 16384,
    windowBits: 0,
    to: ''
  }, options || {});

  var opt = this.options;

  // Force window size for `raw` data, if not set directly,
  // because we have no header for autodetect.
  if (opt.raw && (opt.windowBits >= 0) && (opt.windowBits < 16)) {
    opt.windowBits = -opt.windowBits;
    if (opt.windowBits === 0) { opt.windowBits = -15; }
  }

  // If `windowBits` not defined (and mode not raw) - set autodetect flag for gzip/deflate
  if ((opt.windowBits >= 0) && (opt.windowBits < 16) &&
      !(options && options.windowBits)) {
    opt.windowBits += 32;
  }

  // Gzip header has no info about windows size, we can do autodetect only
  // for deflate. So, if window size not set, force it to max when gzip possible
  if ((opt.windowBits > 15) && (opt.windowBits < 48)) {
    // bit 3 (16) -> gzipped data
    // bit 4 (32) -> autodetect gzip/deflate
    if ((opt.windowBits & 15) === 0) {
      opt.windowBits |= 15;
    }
  }

  this.err    = 0;      // error code, if happens (0 = Z_OK)
  this.msg    = '';     // error message
  this.ended  = false;  // used to avoid multiple onEnd() calls
  this.chunks = [];     // chunks of compressed data

  this.strm   = new ZStream();
  this.strm.avail_out = 0;

  var status  = zlib_inflate.inflateInit2(
    this.strm,
    opt.windowBits
  );

  if (status !== c.Z_OK) {
    throw new Error(msg[status]);
  }

  this.header = new GZheader();

  zlib_inflate.inflateGetHeader(this.strm, this.header);
}

/**
 * Inflate#push(data[, mode]) -> Boolean
 * - data (Uint8Array|Array|ArrayBuffer|String): input data
 * - mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes.
 *   See constants. Skipped or `false` means Z_NO_FLUSH, `true` means Z_FINISH.
 *
 * Sends input data to inflate pipe, generating [[Inflate#onData]] calls with
 * new output chunks. Returns `true` on success. The last data block must have
 * mode Z_FINISH (or `true`). That will flush internal pending buffers and call
 * [[Inflate#onEnd]]. For interim explicit flushes (without ending the stream) you
 * can use mode Z_SYNC_FLUSH, keeping the decompression context.
 *
 * On fail call [[Inflate#onEnd]] with error code and return false.
 *
 * We strongly recommend to use `Uint8Array` on input for best speed (output
 * format is detected automatically). Also, don't skip last param and always
 * use the same type in your code (boolean or number). That will improve JS speed.
 *
 * For regular `Array`-s make sure all elements are [0..255].
 *
 * ##### Example
 *
 * ```javascript
 * push(chunk, false); // push one of data chunks
 * ...
 * push(chunk, true);  // push last chunk
 * ```
 **/
Inflate.prototype.push = function (data, mode) {
  var strm = this.strm;
  var chunkSize = this.options.chunkSize;
  var dictionary = this.options.dictionary;
  var status, _mode;
  var next_out_utf8, tail, utf8str;
  var dict;

  // Flag to properly process Z_BUF_ERROR on testing inflate call
  // when we check that all output data was flushed.
  var allowBufError = false;

  if (this.ended) { return false; }
  _mode = (mode === ~~mode) ? mode : ((mode === true) ? c.Z_FINISH : c.Z_NO_FLUSH);

  // Convert data if needed
  if (typeof data === 'string') {
    // Only binary strings can be decompressed on practice
    strm.input = strings.binstring2buf(data);
  } else if (toString.call(data) === '[object ArrayBuffer]') {
    strm.input = new Uint8Array(data);
  } else {
    strm.input = data;
  }

  strm.next_in = 0;
  strm.avail_in = strm.input.length;

  do {
    if (strm.avail_out === 0) {
      strm.output = new utils.Buf8(chunkSize);
      strm.next_out = 0;
      strm.avail_out = chunkSize;
    }

    status = zlib_inflate.inflate(strm, c.Z_NO_FLUSH);    /* no bad return value */

    if (status === c.Z_NEED_DICT && dictionary) {
      // Convert data if needed
      if (typeof dictionary === 'string') {
        dict = strings.string2buf(dictionary);
      } else if (toString.call(dictionary) === '[object ArrayBuffer]') {
        dict = new Uint8Array(dictionary);
      } else {
        dict = dictionary;
      }

      status = zlib_inflate.inflateSetDictionary(this.strm, dict);

    }

    if (status === c.Z_BUF_ERROR && allowBufError === true) {
      status = c.Z_OK;
      allowBufError = false;
    }

    if (status !== c.Z_STREAM_END && status !== c.Z_OK) {
      this.onEnd(status);
      this.ended = true;
      return false;
    }

    if (strm.next_out) {
      if (strm.avail_out === 0 || status === c.Z_STREAM_END || (strm.avail_in === 0 && (_mode === c.Z_FINISH || _mode === c.Z_SYNC_FLUSH))) {

        if (this.options.to === 'string') {

          next_out_utf8 = strings.utf8border(strm.output, strm.next_out);

          tail = strm.next_out - next_out_utf8;
          utf8str = strings.buf2string(strm.output, next_out_utf8);

          // move tail
          strm.next_out = tail;
          strm.avail_out = chunkSize - tail;
          if (tail) { utils.arraySet(strm.output, strm.output, next_out_utf8, tail, 0); }

          this.onData(utf8str);

        } else {
          this.onData(utils.shrinkBuf(strm.output, strm.next_out));
        }
      }
    }

    // When no more input data, we should check that internal inflate buffers
    // are flushed. The only way to do it when avail_out = 0 - run one more
    // inflate pass. But if output data not exists, inflate return Z_BUF_ERROR.
    // Here we set flag to process this error properly.
    //
    // NOTE. Deflate does not return error in this case and does not needs such
    // logic.
    if (strm.avail_in === 0 && strm.avail_out === 0) {
      allowBufError = true;
    }

  } while ((strm.avail_in > 0 || strm.avail_out === 0) && status !== c.Z_STREAM_END);

  if (status === c.Z_STREAM_END) {
    _mode = c.Z_FINISH;
  }

  // Finalize on the last chunk.
  if (_mode === c.Z_FINISH) {
    status = zlib_inflate.inflateEnd(this.strm);
    this.onEnd(status);
    this.ended = true;
    return status === c.Z_OK;
  }

  // callback interim results if Z_SYNC_FLUSH.
  if (_mode === c.Z_SYNC_FLUSH) {
    this.onEnd(c.Z_OK);
    strm.avail_out = 0;
    return true;
  }

  return true;
};


/**
 * Inflate#onData(chunk) -> Void
 * - chunk (Uint8Array|Array|String): output data. Type of array depends
 *   on js engine support. When string output requested, each chunk
 *   will be string.
 *
 * By default, stores data blocks in `chunks[]` property and glue
 * those in `onEnd`. Override this handler, if you need another behaviour.
 **/
Inflate.prototype.onData = function (chunk) {
  this.chunks.push(chunk);
};


/**
 * Inflate#onEnd(status) -> Void
 * - status (Number): inflate status. 0 (Z_OK) on success,
 *   other if not.
 *
 * Called either after you tell inflate that the input stream is
 * complete (Z_FINISH) or should be flushed (Z_SYNC_FLUSH)
 * or if an error happened. By default - join collected chunks,
 * free memory and fill `results` / `err` properties.
 **/
Inflate.prototype.onEnd = function (status) {
  // On success - join
  if (status === c.Z_OK) {
    if (this.options.to === 'string') {
      // Glue & convert here, until we teach pako to send
      // utf8 aligned strings to onData
      this.result = this.chunks.join('');
    } else {
      this.result = utils.flattenChunks(this.chunks);
    }
  }
  this.chunks = [];
  this.err = status;
  this.msg = this.strm.msg;
};


/**
 * inflate(data[, options]) -> Uint8Array|Array|String
 * - data (Uint8Array|Array|String): input data to decompress.
 * - options (Object): zlib inflate options.
 *
 * Decompress `data` with inflate/ungzip and `options`. Autodetect
 * format via wrapper header by default. That's why we don't provide
 * separate `ungzip` method.
 *
 * Supported options are:
 *
 * - windowBits
 *
 * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
 * for more information.
 *
 * Sugar (options):
 *
 * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify
 *   negative windowBits implicitly.
 * - `to` (String) - if equal to 'string', then result will be converted
 *   from utf8 to utf16 (javascript) string. When string output requested,
 *   chunk length can differ from `chunkSize`, depending on content.
 *
 *
 * ##### Example:
 *
 * ```javascript
 * var pako = require('pako')
 *   , input = pako.deflate([1,2,3,4,5,6,7,8,9])
 *   , output;
 *
 * try {
 *   output = pako.inflate(input);
 * } catch (err)
 *   console.log(err);
 * }
 * ```
 **/
function inflate(input, options) {
  var inflator = new Inflate(options);

  inflator.push(input, true);

  // That will never happens, if you don't cheat with options :)
  if (inflator.err) { throw inflator.msg || msg[inflator.err]; }

  return inflator.result;
}


/**
 * inflateRaw(data[, options]) -> Uint8Array|Array|String
 * - data (Uint8Array|Array|String): input data to decompress.
 * - options (Object): zlib inflate options.
 *
 * The same as [[inflate]], but creates raw data, without wrapper
 * (header and adler32 crc).
 **/
function inflateRaw(input, options) {
  options = options || {};
  options.raw = true;
  return inflate(input, options);
}


/**
 * ungzip(data[, options]) -> Uint8Array|Array|String
 * - data (Uint8Array|Array|String): input data to decompress.
 * - options (Object): zlib inflate options.
 *
 * Just shortcut to [[inflate]], because it autodetects format
 * by header.content. Done for convenience.
 **/


exports.Inflate = Inflate;
exports.inflate = inflate;
exports.inflateRaw = inflateRaw;
exports.ungzip  = inflate;

},{"./utils/common":3,"./utils/strings":4,"./zlib/constants":6,"./zlib/gzheader":9,"./zlib/inflate":11,"./zlib/messages":13,"./zlib/zstream":15}],3:[function(require,module,exports){
'use strict';


var TYPED_OK =  (typeof Uint8Array !== 'undefined') &&
                (typeof Uint16Array !== 'undefined') &&
                (typeof Int32Array !== 'undefined');

function _has(obj, key) {
  return Object.prototype.hasOwnProperty.call(obj, key);
}

exports.assign = function (obj /*from1, from2, from3, ...*/) {
  var sources = Array.prototype.slice.call(arguments, 1);
  while (sources.length) {
    var source = sources.shift();
    if (!source) { continue; }

    if (typeof source !== 'object') {
      throw new TypeError(source + 'must be non-object');
    }

    for (var p in source) {
      if (_has(source, p)) {
        obj[p] = source[p];
      }
    }
  }

  return obj;
};


// reduce buffer size, avoiding mem copy
exports.shrinkBuf = function (buf, size) {
  if (buf.length === size) { return buf; }
  if (buf.subarray) { return buf.subarray(0, size); }
  buf.length = size;
  return buf;
};


var fnTyped = {
  arraySet: function (dest, src, src_offs, len, dest_offs) {
    if (src.subarray && dest.subarray) {
      dest.set(src.subarray(src_offs, src_offs + len), dest_offs);
      return;
    }
    // Fallback to ordinary array
    for (var i = 0; i < len; i++) {
      dest[dest_offs + i] = src[src_offs + i];
    }
  },
  // Join array of chunks to single array.
  flattenChunks: function (chunks) {
    var i, l, len, pos, chunk, result;

    // calculate data length
    len = 0;
    for (i = 0, l = chunks.length; i < l; i++) {
      len += chunks[i].length;
    }

    // join chunks
    result = new Uint8Array(len);
    pos = 0;
    for (i = 0, l = chunks.length; i < l; i++) {
      chunk = chunks[i];
      result.set(chunk, pos);
      pos += chunk.length;
    }

    return result;
  }
};

var fnUntyped = {
  arraySet: function (dest, src, src_offs, len, dest_offs) {
    for (var i = 0; i < len; i++) {
      dest[dest_offs + i] = src[src_offs + i];
    }
  },
  // Join array of chunks to single array.
  flattenChunks: function (chunks) {
    return [].concat.apply([], chunks);
  }
};


// Enable/Disable typed arrays use, for testing
//
exports.setTyped = function (on) {
  if (on) {
    exports.Buf8  = Uint8Array;
    exports.Buf16 = Uint16Array;
    exports.Buf32 = Int32Array;
    exports.assign(exports, fnTyped);
  } else {
    exports.Buf8  = Array;
    exports.Buf16 = Array;
    exports.Buf32 = Array;
    exports.assign(exports, fnUntyped);
  }
};

exports.setTyped(TYPED_OK);

},{}],4:[function(require,module,exports){
// String encode/decode helpers
'use strict';


var utils = require('./common');


// Quick check if we can use fast array to bin string conversion
//
// - apply(Array) can fail on Android 2.2
// - apply(Uint8Array) can fail on iOS 5.1 Safari
//
var STR_APPLY_OK = true;
var STR_APPLY_UIA_OK = true;

try { String.fromCharCode.apply(null, [ 0 ]); } catch (__) { STR_APPLY_OK = false; }
try { String.fromCharCode.apply(null, new Uint8Array(1)); } catch (__) { STR_APPLY_UIA_OK = false; }


// Table with utf8 lengths (calculated by first byte of sequence)
// Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS,
// because max possible codepoint is 0x10ffff
var _utf8len = new utils.Buf8(256);
for (var q = 0; q < 256; q++) {
  _utf8len[q] = (q >= 252 ? 6 : q >= 248 ? 5 : q >= 240 ? 4 : q >= 224 ? 3 : q >= 192 ? 2 : 1);
}
_utf8len[254] = _utf8len[254] = 1; // Invalid sequence start


// convert string to array (typed, when possible)
exports.string2buf = function (str) {
  var buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0;

  // count binary size
  for (m_pos = 0; m_pos < str_len; m_pos++) {
    c = str.charCodeAt(m_pos);
    if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) {
      c2 = str.charCodeAt(m_pos + 1);
      if ((c2 & 0xfc00) === 0xdc00) {
        c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00);
        m_pos++;
      }
    }
    buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4;
  }

  // allocate buffer
  buf = new utils.Buf8(buf_len);

  // convert
  for (i = 0, m_pos = 0; i < buf_len; m_pos++) {
    c = str.charCodeAt(m_pos);
    if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) {
      c2 = str.charCodeAt(m_pos + 1);
      if ((c2 & 0xfc00) === 0xdc00) {
        c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00);
        m_pos++;
      }
    }
    if (c < 0x80) {
      /* one byte */
      buf[i++] = c;
    } else if (c < 0x800) {
      /* two bytes */
      buf[i++] = 0xC0 | (c >>> 6);
      buf[i++] = 0x80 | (c & 0x3f);
    } else if (c < 0x10000) {
      /* three bytes */
      buf[i++] = 0xE0 | (c >>> 12);
      buf[i++] = 0x80 | (c >>> 6 & 0x3f);
      buf[i++] = 0x80 | (c & 0x3f);
    } else {
      /* four bytes */
      buf[i++] = 0xf0 | (c >>> 18);
      buf[i++] = 0x80 | (c >>> 12 & 0x3f);
      buf[i++] = 0x80 | (c >>> 6 & 0x3f);
      buf[i++] = 0x80 | (c & 0x3f);
    }
  }

  return buf;
};

// Helper (used in 2 places)
function buf2binstring(buf, len) {
  // use fallback for big arrays to avoid stack overflow
  if (len < 65537) {
    if ((buf.subarray && STR_APPLY_UIA_OK) || (!buf.subarray && STR_APPLY_OK)) {
      return String.fromCharCode.apply(null, utils.shrinkBuf(buf, len));
    }
  }

  var result = '';
  for (var i = 0; i < len; i++) {
    result += String.fromCharCode(buf[i]);
  }
  return result;
}


// Convert byte array to binary string
exports.buf2binstring = function (buf) {
  return buf2binstring(buf, buf.length);
};


// Convert binary string (typed, when possible)
exports.binstring2buf = function (str) {
  var buf = new utils.Buf8(str.length);
  for (var i = 0, len = buf.length; i < len; i++) {
    buf[i] = str.charCodeAt(i);
  }
  return buf;
};


// convert array to string
exports.buf2string = function (buf, max) {
  var i, out, c, c_len;
  var len = max || buf.length;

  // Reserve max possible length (2 words per char)
  // NB: by unknown reasons, Array is significantly faster for
  //     String.fromCharCode.apply than Uint16Array.
  var utf16buf = new Array(len * 2);

  for (out = 0, i = 0; i < len;) {
    c = buf[i++];
    // quick process ascii
    if (c < 0x80) { utf16buf[out++] = c; continue; }

    c_len = _utf8len[c];
    // skip 5 & 6 byte codes
    if (c_len > 4) { utf16buf[out++] = 0xfffd; i += c_len - 1; continue; }

    // apply mask on first byte
    c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07;
    // join the rest
    while (c_len > 1 && i < len) {
      c = (c << 6) | (buf[i++] & 0x3f);
      c_len--;
    }

    // terminated by end of string?
    if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; }

    if (c < 0x10000) {
      utf16buf[out++] = c;
    } else {
      c -= 0x10000;
      utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff);
      utf16buf[out++] = 0xdc00 | (c & 0x3ff);
    }
  }

  return buf2binstring(utf16buf, out);
};


// Calculate max possible position in utf8 buffer,
// that will not break sequence. If that's not possible
// - (very small limits) return max size as is.
//
// buf[] - utf8 bytes array
// max   - length limit (mandatory);
exports.utf8border = function (buf, max) {
  var pos;

  max = max || buf.length;
  if (max > buf.length) { max = buf.length; }

  // go back from last position, until start of sequence found
  pos = max - 1;
  while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; }

  // Very small and broken sequence,
  // return max, because we should return something anyway.
  if (pos < 0) { return max; }

  // If we came to start of buffer - that means buffer is too small,
  // return max too.
  if (pos === 0) { return max; }

  return (pos + _utf8len[buf[pos]] > max) ? pos : max;
};

},{"./common":3}],5:[function(require,module,exports){
'use strict';

// Note: adler32 takes 12% for level 0 and 2% for level 6.
// It isn't worth it to make additional optimizations as in original.
// Small size is preferable.

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

function adler32(adler, buf, len, pos) {
  var s1 = (adler & 0xffff) |0,
      s2 = ((adler >>> 16) & 0xffff) |0,
      n = 0;

  while (len !== 0) {
    // Set limit ~ twice less than 5552, to keep
    // s2 in 31-bits, because we force signed ints.
    // in other case %= will fail.
    n = len > 2000 ? 2000 : len;
    len -= n;

    do {
      s1 = (s1 + buf[pos++]) |0;
      s2 = (s2 + s1) |0;
    } while (--n);

    s1 %= 65521;
    s2 %= 65521;
  }

  return (s1 | (s2 << 16)) |0;
}


module.exports = adler32;

},{}],6:[function(require,module,exports){
'use strict';

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

module.exports = {

  /* Allowed flush values; see deflate() and inflate() below for details */
  Z_NO_FLUSH:         0,
  Z_PARTIAL_FLUSH:    1,
  Z_SYNC_FLUSH:       2,
  Z_FULL_FLUSH:       3,
  Z_FINISH:           4,
  Z_BLOCK:            5,
  Z_TREES:            6,

  /* Return codes for the compression/decompression functions. Negative values
  * are errors, positive values are used for special but normal events.
  */
  Z_OK:               0,
  Z_STREAM_END:       1,
  Z_NEED_DICT:        2,
  Z_ERRNO:           -1,
  Z_STREAM_ERROR:    -2,
  Z_DATA_ERROR:      -3,
  //Z_MEM_ERROR:     -4,
  Z_BUF_ERROR:       -5,
  //Z_VERSION_ERROR: -6,

  /* compression levels */
  Z_NO_COMPRESSION:         0,
  Z_BEST_SPEED:             1,
  Z_BEST_COMPRESSION:       9,
  Z_DEFAULT_COMPRESSION:   -1,


  Z_FILTERED:               1,
  Z_HUFFMAN_ONLY:           2,
  Z_RLE:                    3,
  Z_FIXED:                  4,
  Z_DEFAULT_STRATEGY:       0,

  /* Possible values of the data_type field (though see inflate()) */
  Z_BINARY:                 0,
  Z_TEXT:                   1,
  //Z_ASCII:                1, // = Z_TEXT (deprecated)
  Z_UNKNOWN:                2,

  /* The deflate compression method */
  Z_DEFLATED:               8
  //Z_NULL:                 null // Use -1 or null inline, depending on var type
};

},{}],7:[function(require,module,exports){
'use strict';

// Note: we can't get significant speed boost here.
// So write code to minimize size - no pregenerated tables
// and array tools dependencies.

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

// Use ordinary array, since untyped makes no boost here
function makeTable() {
  var c, table = [];

  for (var n = 0; n < 256; n++) {
    c = n;
    for (var k = 0; k < 8; k++) {
      c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
    }
    table[n] = c;
  }

  return table;
}

// Create table on load. Just 255 signed longs. Not a problem.
var crcTable = makeTable();


function crc32(crc, buf, len, pos) {
  var t = crcTable,
      end = pos + len;

  crc ^= -1;

  for (var i = pos; i < end; i++) {
    crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF];
  }

  return (crc ^ (-1)); // >>> 0;
}


module.exports = crc32;

},{}],8:[function(require,module,exports){
'use strict';

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

var utils   = require('../utils/common');
var trees   = require('./trees');
var adler32 = require('./adler32');
var crc32   = require('./crc32');
var msg     = require('./messages');

/* Public constants ==========================================================*/
/* ===========================================================================*/


/* Allowed flush values; see deflate() and inflate() below for details */
var Z_NO_FLUSH      = 0;
var Z_PARTIAL_FLUSH = 1;
//var Z_SYNC_FLUSH    = 2;
var Z_FULL_FLUSH    = 3;
var Z_FINISH        = 4;
var Z_BLOCK         = 5;
//var Z_TREES         = 6;


/* Return codes for the compression/decompression functions. Negative values
 * are errors, positive values are used for special but normal events.
 */
var Z_OK            = 0;
var Z_STREAM_END    = 1;
//var Z_NEED_DICT     = 2;
//var Z_ERRNO         = -1;
var Z_STREAM_ERROR  = -2;
var Z_DATA_ERROR    = -3;
//var Z_MEM_ERROR     = -4;
var Z_BUF_ERROR     = -5;
//var Z_VERSION_ERROR = -6;


/* compression levels */
//var Z_NO_COMPRESSION      = 0;
//var Z_BEST_SPEED          = 1;
//var Z_BEST_COMPRESSION    = 9;
var Z_DEFAULT_COMPRESSION = -1;


var Z_FILTERED            = 1;
var Z_HUFFMAN_ONLY        = 2;
var Z_RLE                 = 3;
var Z_FIXED               = 4;
var Z_DEFAULT_STRATEGY    = 0;

/* Possible values of the data_type field (though see inflate()) */
//var Z_BINARY              = 0;
//var Z_TEXT                = 1;
//var Z_ASCII               = 1; // = Z_TEXT
var Z_UNKNOWN             = 2;


/* The deflate compression method */
var Z_DEFLATED  = 8;

/*============================================================================*/


var MAX_MEM_LEVEL = 9;
/* Maximum value for memLevel in deflateInit2 */
var MAX_WBITS = 15;
/* 32K LZ77 window */
var DEF_MEM_LEVEL = 8;


var LENGTH_CODES  = 29;
/* number of length codes, not counting the special END_BLOCK code */
var LITERALS      = 256;
/* number of literal bytes 0..255 */
var L_CODES       = LITERALS + 1 + LENGTH_CODES;
/* number of Literal or Length codes, including the END_BLOCK code */
var D_CODES       = 30;
/* number of distance codes */
var BL_CODES      = 19;
/* number of codes used to transfer the bit lengths */
var HEAP_SIZE     = 2 * L_CODES + 1;
/* maximum heap size */
var MAX_BITS  = 15;
/* All codes must not exceed MAX_BITS bits */

var MIN_MATCH = 3;
var MAX_MATCH = 258;
var MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1);

var PRESET_DICT = 0x20;

var INIT_STATE = 42;
var EXTRA_STATE = 69;
var NAME_STATE = 73;
var COMMENT_STATE = 91;
var HCRC_STATE = 103;
var BUSY_STATE = 113;
var FINISH_STATE = 666;

var BS_NEED_MORE      = 1; /* block not completed, need more input or more output */
var BS_BLOCK_DONE     = 2; /* block flush performed */
var BS_FINISH_STARTED = 3; /* finish started, need only more output at next deflate */
var BS_FINISH_DONE    = 4; /* finish done, accept no more input or output */

var OS_CODE = 0x03; // Unix :) . Don't detect, use this default.

function err(strm, errorCode) {
  strm.msg = msg[errorCode];
  return errorCode;
}

function rank(f) {
  return ((f) << 1) - ((f) > 4 ? 9 : 0);
}

function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } }


/* =========================================================================
 * Flush as much pending output as possible. All deflate() output goes
 * through this function so some applications may wish to modify it
 * to avoid allocating a large strm->output buffer and copying into it.
 * (See also read_buf()).
 */
function flush_pending(strm) {
  var s = strm.state;

  //_tr_flush_bits(s);
  var len = s.pending;
  if (len > strm.avail_out) {
    len = strm.avail_out;
  }
  if (len === 0) { return; }

  utils.arraySet(strm.output, s.pending_buf, s.pending_out, len, strm.next_out);
  strm.next_out += len;
  s.pending_out += len;
  strm.total_out += len;
  strm.avail_out -= len;
  s.pending -= len;
  if (s.pending === 0) {
    s.pending_out = 0;
  }
}


function flush_block_only(s, last) {
  trees._tr_flush_block(s, (s.block_start >= 0 ? s.block_start : -1), s.strstart - s.block_start, last);
  s.block_start = s.strstart;
  flush_pending(s.strm);
}


function put_byte(s, b) {
  s.pending_buf[s.pending++] = b;
}


/* =========================================================================
 * Put a short in the pending buffer. The 16-bit value is put in MSB order.
 * IN assertion: the stream state is correct and there is enough room in
 * pending_buf.
 */
function putShortMSB(s, b) {
//  put_byte(s, (Byte)(b >> 8));
//  put_byte(s, (Byte)(b & 0xff));
  s.pending_buf[s.pending++] = (b >>> 8) & 0xff;
  s.pending_buf[s.pending++] = b & 0xff;
}


/* ===========================================================================
 * Read a new buffer from the current input stream, update the adler32
 * and total number of bytes read.  All deflate() input goes through
 * this function so some applications may wish to modify it to avoid
 * allocating a large strm->input buffer and copying from it.
 * (See also flush_pending()).
 */
function read_buf(strm, buf, start, size) {
  var len = strm.avail_in;

  if (len > size) { len = size; }
  if (len === 0) { return 0; }

  strm.avail_in -= len;

  // zmemcpy(buf, strm->next_in, len);
  utils.arraySet(buf, strm.input, strm.next_in, len, start);
  if (strm.state.wrap === 1) {
    strm.adler = adler32(strm.adler, buf, len, start);
  }

  else if (strm.state.wrap === 2) {
    strm.adler = crc32(strm.adler, buf, len, start);
  }

  strm.next_in += len;
  strm.total_in += len;

  return len;
}


/* ===========================================================================
 * Set match_start to the longest match starting at the given string and
 * return its length. Matches shorter or equal to prev_length are discarded,
 * in which case the result is equal to prev_length and match_start is
 * garbage.
 * IN assertions: cur_match is the head of the hash chain for the current
 *   string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1
 * OUT assertion: the match length is not greater than s->lookahead.
 */
function longest_match(s, cur_match) {
  var chain_length = s.max_chain_length;      /* max hash chain length */
  var scan = s.strstart; /* current string */
  var match;                       /* matched string */
  var len;                           /* length of current match */
  var best_len = s.prev_length;              /* best match length so far */
  var nice_match = s.nice_match;             /* stop if match long enough */
  var limit = (s.strstart > (s.w_size - MIN_LOOKAHEAD)) ?
      s.strstart - (s.w_size - MIN_LOOKAHEAD) : 0/*NIL*/;

  var _win = s.window; // shortcut

  var wmask = s.w_mask;
  var prev  = s.prev;

  /* Stop when cur_match becomes <= limit. To simplify the code,
   * we prevent matches with the string of window index 0.
   */

  var strend = s.strstart + MAX_MATCH;
  var scan_end1  = _win[scan + best_len - 1];
  var scan_end   = _win[scan + best_len];

  /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16.
   * It is easy to get rid of this optimization if necessary.
   */
  // Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever");

  /* Do not waste too much time if we already have a good match: */
  if (s.prev_length >= s.good_match) {
    chain_length >>= 2;
  }
  /* Do not look for matches beyond the end of the input. This is necessary
   * to make deflate deterministic.
   */
  if (nice_match > s.lookahead) { nice_match = s.lookahead; }

  // Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead");

  do {
    // Assert(cur_match < s->strstart, "no future");
    match = cur_match;

    /* Skip to next match if the match length cannot increase
     * or if the match length is less than 2.  Note that the checks below
     * for insufficient lookahead only occur occasionally for performance
     * reasons.  Therefore uninitialized memory will be accessed, and
     * conditional jumps will be made that depend on those values.
     * However the length of the match is limited to the lookahead, so
     * the output of deflate is not affected by the uninitialized values.
     */

    if (_win[match + best_len]     !== scan_end  ||
        _win[match + best_len - 1] !== scan_end1 ||
        _win[match]                !== _win[scan] ||
        _win[++match]              !== _win[scan + 1]) {
      continue;
    }

    /* The check at best_len-1 can be removed because it will be made
     * again later. (This heuristic is not always a win.)
     * It is not necessary to compare scan[2] and match[2] since they
     * are always equal when the other bytes match, given that
     * the hash keys are equal and that HASH_BITS >= 8.
     */
    scan += 2;
    match++;
    // Assert(*scan == *match, "match[2]?");

    /* We check for insufficient lookahead only every 8th comparison;
     * the 256th check will be made at strstart+258.
     */
    do {
      /*jshint noempty:false*/
    } while (_win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
             _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
             _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
             _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
             scan < strend);

    // Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");

    len = MAX_MATCH - (strend - scan);
    scan = strend - MAX_MATCH;

    if (len > best_len) {
      s.match_start = cur_match;
      best_len = len;
      if (len >= nice_match) {
        break;
      }
      scan_end1  = _win[scan + best_len - 1];
      scan_end   = _win[scan + best_len];
    }
  } while ((cur_match = prev[cur_match & wmask]) > limit && --chain_length !== 0);

  if (best_len <= s.lookahead) {
    return best_len;
  }
  return s.lookahead;
}


/* ===========================================================================
 * Fill the window when the lookahead becomes insufficient.
 * Updates strstart and lookahead.
 *
 * IN assertion: lookahead < MIN_LOOKAHEAD
 * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD
 *    At least one byte has been read, or avail_in == 0; reads are
 *    performed for at least two bytes (required for the zip translate_eol
 *    option -- not supported here).
 */
function fill_window(s) {
  var _w_size = s.w_size;
  var p, n, m, more, str;

  //Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead");

  do {
    more = s.window_size - s.lookahead - s.strstart;

    // JS ints have 32 bit, block below not needed
    /* Deal with !@#$% 64K limit: */
    //if (sizeof(int) <= 2) {
    //    if (more == 0 && s->strstart == 0 && s->lookahead == 0) {
    //        more = wsize;
    //
    //  } else if (more == (unsigned)(-1)) {
    //        /* Very unlikely, but possible on 16 bit machine if
    //         * strstart == 0 && lookahead == 1 (input done a byte at time)
    //         */
    //        more--;
    //    }
    //}


    /* If the window is almost full and there is insufficient lookahead,
     * move the upper half to the lower one to make room in the upper half.
     */
    if (s.strstart >= _w_size + (_w_size - MIN_LOOKAHEAD)) {

      utils.arraySet(s.window, s.window, _w_size, _w_size, 0);
      s.match_start -= _w_size;
      s.strstart -= _w_size;
      /* we now have strstart >= MAX_DIST */
      s.block_start -= _w_size;

      /* Slide the hash table (could be avoided with 32 bit values
       at the expense of memory usage). We slide even when level == 0
       to keep the hash table consistent if we switch back to level > 0
       later. (Using level 0 permanently is not an optimal usage of
       zlib, so we don't care about this pathological case.)
       */

      n = s.hash_size;
      p = n;
      do {
        m = s.head[--p];
        s.head[p] = (m >= _w_size ? m - _w_size : 0);
      } while (--n);

      n = _w_size;
      p = n;
      do {
        m = s.prev[--p];
        s.prev[p] = (m >= _w_size ? m - _w_size : 0);
        /* If n is not on any hash chain, prev[n] is garbage but
         * its value will never be used.
         */
      } while (--n);

      more += _w_size;
    }
    if (s.strm.avail_in === 0) {
      break;
    }

    /* If there was no sliding:
     *    strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 &&
     *    more == window_size - lookahead - strstart
     * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1)
     * => more >= window_size - 2*WSIZE + 2
     * In the BIG_MEM or MMAP case (not yet supported),
     *   window_size == input_size + MIN_LOOKAHEAD  &&
     *   strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD.
     * Otherwise, window_size == 2*WSIZE so more >= 2.
     * If there was sliding, more >= WSIZE. So in all cases, more >= 2.
     */
    //Assert(more >= 2, "more < 2");
    n = read_buf(s.strm, s.window, s.strstart + s.lookahead, more);
    s.lookahead += n;

    /* Initialize the hash value now that we have some input: */
    if (s.lookahead + s.insert >= MIN_MATCH) {
      str = s.strstart - s.insert;
      s.ins_h = s.window[str];

      /* UPDATE_HASH(s, s->ins_h, s->window[str + 1]); */
      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + 1]) & s.hash_mask;
//#if MIN_MATCH != 3
//        Call update_hash() MIN_MATCH-3 more times
//#endif
      while (s.insert) {
        /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */
        s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask;

        s.prev[str & s.w_mask] = s.head[s.ins_h];
        s.head[s.ins_h] = str;
        str++;
        s.insert--;
        if (s.lookahead + s.insert < MIN_MATCH) {
          break;
        }
      }
    }
    /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage,
     * but this is not important since only literal bytes will be emitted.
     */

  } while (s.lookahead < MIN_LOOKAHEAD && s.strm.avail_in !== 0);

  /* If the WIN_INIT bytes after the end of the current data have never been
   * written, then zero those bytes in order to avoid memory check reports of
   * the use of uninitialized (or uninitialised as Julian writes) bytes by
   * the longest match routines.  Update the high water mark for the next
   * time through here.  WIN_INIT is set to MAX_MATCH since the longest match
   * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead.
   */
//  if (s.high_water < s.window_size) {
//    var curr = s.strstart + s.lookahead;
//    var init = 0;
//
//    if (s.high_water < curr) {
//      /* Previous high water mark below current data -- zero WIN_INIT
//       * bytes or up to end of window, whichever is less.
//       */
//      init = s.window_size - curr;
//      if (init > WIN_INIT)
//        init = WIN_INIT;
//      zmemzero(s->window + curr, (unsigned)init);
//      s->high_water = curr + init;
//    }
//    else if (s->high_water < (ulg)curr + WIN_INIT) {
//      /* High water mark at or above current data, but below current data
//       * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up
//       * to end of window, whichever is less.
//       */
//      init = (ulg)curr + WIN_INIT - s->high_water;
//      if (init > s->window_size - s->high_water)
//        init = s->window_size - s->high_water;
//      zmemzero(s->window + s->high_water, (unsigned)init);
//      s->high_water += init;
//    }
//  }
//
//  Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD,
//    "not enough room for search");
}

/* ===========================================================================
 * Copy without compression as much as possible from the input stream, return
 * the current block state.
 * This function does not insert new strings in the dictionary since
 * uncompressible data is probably not useful. This function is used
 * only for the level=0 compression option.
 * NOTE: this function should be optimized to avoid extra copying from
 * window to pending_buf.
 */
function deflate_stored(s, flush) {
  /* Stored blocks are limited to 0xffff bytes, pending_buf is limited
   * to pending_buf_size, and each stored block has a 5 byte header:
   */
  var max_block_size = 0xffff;

  if (max_block_size > s.pending_buf_size - 5) {
    max_block_size = s.pending_buf_size - 5;
  }

  /* Copy as much as possible from input to output: */
  for (;;) {
    /* Fill the window as much as possible: */
    if (s.lookahead <= 1) {

      //Assert(s->strstart < s->w_size+MAX_DIST(s) ||
      //  s->block_start >= (long)s->w_size, "slide too late");
//      if (!(s.strstart < s.w_size + (s.w_size - MIN_LOOKAHEAD) ||
//        s.block_start >= s.w_size)) {
//        throw  new Error("slide too late");
//      }

      fill_window(s);
      if (s.lookahead === 0 && flush === Z_NO_FLUSH) {
        return BS_NEED_MORE;
      }

      if (s.lookahead === 0) {
        break;
      }
      /* flush the current block */
    }
    //Assert(s->block_start >= 0L, "block gone");
//    if (s.block_start < 0) throw new Error("block gone");

    s.strstart += s.lookahead;
    s.lookahead = 0;

    /* Emit a stored block if pending_buf will be full: */
    var max_start = s.block_start + max_block_size;

    if (s.strstart === 0 || s.strstart >= max_start) {
      /* strstart == 0 is possible when wraparound on 16-bit machine */
      s.lookahead = s.strstart - max_start;
      s.strstart = max_start;
      /*** FLUSH_BLOCK(s, 0); ***/
      flush_block_only(s, false);
      if (s.strm.avail_out === 0) {
        return BS_NEED_MORE;
      }
      /***/


    }
    /* Flush if we may have to slide, otherwise block_start may become
     * negative and the data will be gone:
     */
    if (s.strstart - s.block_start >= (s.w_size - MIN_LOOKAHEAD)) {
      /*** FLUSH_BLOCK(s, 0); ***/
      flush_block_only(s, false);
      if (s.strm.avail_out === 0) {
        return BS_NEED_MORE;
      }
      /***/
    }
  }

  s.insert = 0;

  if (flush === Z_FINISH) {
    /*** FLUSH_BLOCK(s, 1); ***/
    flush_block_only(s, true);
    if (s.strm.avail_out === 0) {
      return BS_FINISH_STARTED;
    }
    /***/
    return BS_FINISH_DONE;
  }

  if (s.strstart > s.block_start) {
    /*** FLUSH_BLOCK(s, 0); ***/
    flush_block_only(s, false);
    if (s.strm.avail_out === 0) {
      return BS_NEED_MORE;
    }
    /***/
  }

  return BS_NEED_MORE;
}

/* ===========================================================================
 * Compress as much as possible from the input stream, return the current
 * block state.
 * This function does not perform lazy evaluation of matches and inserts
 * new strings in the dictionary only for unmatched strings or for short
 * matches. It is used only for the fast compression options.
 */
function deflate_fast(s, flush) {
  var hash_head;        /* head of the hash chain */
  var bflush;           /* set if current block must be flushed */

  for (;;) {
    /* Make sure that we always have enough lookahead, except
     * at the end of the input file. We need MAX_MATCH bytes
     * for the next match, plus MIN_MATCH bytes to insert the
     * string following the next match.
     */
    if (s.lookahead < MIN_LOOKAHEAD) {
      fill_window(s);
      if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) {
        return BS_NEED_MORE;
      }
      if (s.lookahead === 0) {
        break; /* flush the current block */
      }
    }

    /* Insert the string window[strstart .. strstart+2] in the
     * dictionary, and set hash_head to the head of the hash chain:
     */
    hash_head = 0/*NIL*/;
    if (s.lookahead >= MIN_MATCH) {
      /*** INSERT_STRING(s, s.strstart, hash_head); ***/
      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
      hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
      s.head[s.ins_h] = s.strstart;
      /***/
    }

    /* Find the longest match, discarding those <= prev_length.
     * At this point we have always match_length < MIN_MATCH
     */
    if (hash_head !== 0/*NIL*/ && ((s.strstart - hash_head) <= (s.w_size - MIN_LOOKAHEAD))) {
      /* To simplify the code, we prevent matches with the string
       * of window index 0 (in particular we have to avoid a match
       * of the string with itself at the start of the input file).
       */
      s.match_length = longest_match(s, hash_head);
      /* longest_match() sets match_start */
    }
    if (s.match_length >= MIN_MATCH) {
      // check_match(s, s.strstart, s.match_start, s.match_length); // for debug only

      /*** _tr_tally_dist(s, s.strstart - s.match_start,
                     s.match_length - MIN_MATCH, bflush); ***/
      bflush = trees._tr_tally(s, s.strstart - s.match_start, s.match_length - MIN_MATCH);

      s.lookahead -= s.match_length;

      /* Insert new strings in the hash table only if the match length
       * is not too large. This saves time but degrades compression.
       */
      if (s.match_length <= s.max_lazy_match/*max_insert_length*/ && s.lookahead >= MIN_MATCH) {
        s.match_length--; /* string at strstart already in table */
        do {
          s.strstart++;
          /*** INSERT_STRING(s, s.strstart, hash_head); ***/
          s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
          hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
          s.head[s.ins_h] = s.strstart;
          /***/
          /* strstart never exceeds WSIZE-MAX_MATCH, so there are
           * always MIN_MATCH bytes ahead.
           */
        } while (--s.match_length !== 0);
        s.strstart++;
      } else
      {
        s.strstart += s.match_length;
        s.match_length = 0;
        s.ins_h = s.window[s.strstart];
        /* UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); */
        s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + 1]) & s.hash_mask;

//#if MIN_MATCH != 3
//                Call UPDATE_HASH() MIN_MATCH-3 more times
//#endif
        /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not
         * matter since it will be recomputed at next deflate call.
         */
      }
    } else {
      /* No match, output a literal byte */
      //Tracevv((stderr,"%c", s.window[s.strstart]));
      /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
      bflush = trees._tr_tally(s, 0, s.window[s.strstart]);

      s.lookahead--;
      s.strstart++;
    }
    if (bflush) {
      /*** FLUSH_BLOCK(s, 0); ***/
      flush_block_only(s, false);
      if (s.strm.avail_out === 0) {
        return BS_NEED_MORE;
      }
      /***/
    }
  }
  s.insert = ((s.strstart < (MIN_MATCH - 1)) ? s.strstart : MIN_MATCH - 1);
  if (flush === Z_FINISH) {
    /*** FLUSH_BLOCK(s, 1); ***/
    flush_block_only(s, true);
    if (s.strm.avail_out === 0) {
      return BS_FINISH_STARTED;
    }
    /***/
    return BS_FINISH_DONE;
  }
  if (s.last_lit) {
    /*** FLUSH_BLOCK(s, 0); ***/
    flush_block_only(s, false);
    if (s.strm.avail_out === 0) {
      return BS_NEED_MORE;
    }
    /***/
  }
  return BS_BLOCK_DONE;
}

/* ===========================================================================
 * Same as above, but achieves better compression. We use a lazy
 * evaluation for matches: a match is finally adopted only if there is
 * no better match at the next window position.
 */
function deflate_slow(s, flush) {
  var hash_head;          /* head of hash chain */
  var bflush;              /* set if current block must be flushed */

  var max_insert;

  /* Process the input block. */
  for (;;) {
    /* Make sure that we always have enough lookahead, except
     * at the end of the input file. We need MAX_MATCH bytes
     * for the next match, plus MIN_MATCH bytes to insert the
     * string following the next match.
     */
    if (s.lookahead < MIN_LOOKAHEAD) {
      fill_window(s);
      if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) {
        return BS_NEED_MORE;
      }
      if (s.lookahead === 0) { break; } /* flush the current block */
    }

    /* Insert the string window[strstart .. strstart+2] in the
     * dictionary, and set hash_head to the head of the hash chain:
     */
    hash_head = 0/*NIL*/;
    if (s.lookahead >= MIN_MATCH) {
      /*** INSERT_STRING(s, s.strstart, hash_head); ***/
      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
      hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
      s.head[s.ins_h] = s.strstart;
      /***/
    }

    /* Find the longest match, discarding those <= prev_length.
     */
    s.prev_length = s.match_length;
    s.prev_match = s.match_start;
    s.match_length = MIN_MATCH - 1;

    if (hash_head !== 0/*NIL*/ && s.prev_length < s.max_lazy_match &&
        s.strstart - hash_head <= (s.w_size - MIN_LOOKAHEAD)/*MAX_DIST(s)*/) {
      /* To simplify the code, we prevent matches with the string
       * of window index 0 (in particular we have to avoid a match
       * of the string with itself at the start of the input file).
       */
      s.match_length = longest_match(s, hash_head);
      /* longest_match() sets match_start */

      if (s.match_length <= 5 &&
         (s.strategy === Z_FILTERED || (s.match_length === MIN_MATCH && s.strstart - s.match_start > 4096/*TOO_FAR*/))) {

        /* If prev_match is also MIN_MATCH, match_start is garbage
         * but we will ignore the current match anyway.
         */
        s.match_length = MIN_MATCH - 1;
      }
    }
    /* If there was a match at the previous step and the current
     * match is not better, output the previous match:
     */
    if (s.prev_length >= MIN_MATCH && s.match_length <= s.prev_length) {
      max_insert = s.strstart + s.lookahead - MIN_MATCH;
      /* Do not insert strings in hash table beyond this. */

      //check_match(s, s.strstart-1, s.prev_match, s.prev_length);

      /***_tr_tally_dist(s, s.strstart - 1 - s.prev_match,
                     s.prev_length - MIN_MATCH, bflush);***/
      bflush = trees._tr_tally(s, s.strstart - 1 - s.prev_match, s.prev_length - MIN_MATCH);
      /* Insert in hash table all strings up to the end of the match.
       * strstart-1 and strstart are already inserted. If there is not
       * enough lookahead, the last two strings are not inserted in
       * the hash table.
       */
      s.lookahead -= s.prev_length - 1;
      s.prev_length -= 2;
      do {
        if (++s.strstart <= max_insert) {
          /*** INSERT_STRING(s, s.strstart, hash_head); ***/
          s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[s.strstart + MIN_MATCH - 1]) & s.hash_mask;
          hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
          s.head[s.ins_h] = s.strstart;
          /***/
        }
      } while (--s.prev_length !== 0);
      s.match_available = 0;
      s.match_length = MIN_MATCH - 1;
      s.strstart++;

      if (bflush) {
        /*** FLUSH_BLOCK(s, 0); ***/
        flush_block_only(s, false);
        if (s.strm.avail_out === 0) {
          return BS_NEED_MORE;
        }
        /***/
      }

    } else if (s.match_available) {
      /* If there was no match at the previous position, output a
       * single literal. If there was a match but the current match
       * is longer, truncate the previous match to a single literal.
       */
      //Tracevv((stderr,"%c", s->window[s->strstart-1]));
      /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/
      bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]);

      if (bflush) {
        /*** FLUSH_BLOCK_ONLY(s, 0) ***/
        flush_block_only(s, false);
        /***/
      }
      s.strstart++;
      s.lookahead--;
      if (s.strm.avail_out === 0) {
        return BS_NEED_MORE;
      }
    } else {
      /* There is no previous match to compare with, wait for
       * the next step to decide.
       */
      s.match_available = 1;
      s.strstart++;
      s.lookahead--;
    }
  }
  //Assert (flush != Z_NO_FLUSH, "no flush?");
  if (s.match_available) {
    //Tracevv((stderr,"%c", s->window[s->strstart-1]));
    /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/
    bflush = trees._tr_tally(s, 0, s.window[s.strstart - 1]);

    s.match_available = 0;
  }
  s.insert = s.strstart < MIN_MATCH - 1 ? s.strstart : MIN_MATCH - 1;
  if (flush === Z_FINISH) {
    /*** FLUSH_BLOCK(s, 1); ***/
    flush_block_only(s, true);
    if (s.strm.avail_out === 0) {
      return BS_FINISH_STARTED;
    }
    /***/
    return BS_FINISH_DONE;
  }
  if (s.last_lit) {
    /*** FLUSH_BLOCK(s, 0); ***/
    flush_block_only(s, false);
    if (s.strm.avail_out === 0) {
      return BS_NEED_MORE;
    }
    /***/
  }

  return BS_BLOCK_DONE;
}


/* ===========================================================================
 * For Z_RLE, simply look for runs of bytes, generate matches only of distance
 * one.  Do not maintain a hash table.  (It will be regenerated if this run of
 * deflate switches away from Z_RLE.)
 */
function deflate_rle(s, flush) {
  var bflush;            /* set if current block must be flushed */
  var prev;              /* byte at distance one to match */
  var scan, strend;      /* scan goes up to strend for length of run */

  var _win = s.window;

  for (;;) {
    /* Make sure that we always have enough lookahead, except
     * at the end of the input file. We need MAX_MATCH bytes
     * for the longest run, plus one for the unrolled loop.
     */
    if (s.lookahead <= MAX_MATCH) {
      fill_window(s);
      if (s.lookahead <= MAX_MATCH && flush === Z_NO_FLUSH) {
        return BS_NEED_MORE;
      }
      if (s.lookahead === 0) { break; } /* flush the current block */
    }

    /* See how many times the previous byte repeats */
    s.match_length = 0;
    if (s.lookahead >= MIN_MATCH && s.strstart > 0) {
      scan = s.strstart - 1;
      prev = _win[scan];
      if (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan]) {
        strend = s.strstart + MAX_MATCH;
        do {
          /*jshint noempty:false*/
        } while (prev === _win[++scan] && prev === _win[++scan] &&
                 prev === _win[++scan] && prev === _win[++scan] &&
                 prev === _win[++scan] && prev === _win[++scan] &&
                 prev === _win[++scan] && prev === _win[++scan] &&
                 scan < strend);
        s.match_length = MAX_MATCH - (strend - scan);
        if (s.match_length > s.lookahead) {
          s.match_length = s.lookahead;
        }
      }
      //Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan");
    }

    /* Emit match if have run of MIN_MATCH or longer, else emit literal */
    if (s.match_length >= MIN_MATCH) {
      //check_match(s, s.strstart, s.strstart - 1, s.match_length);

      /*** _tr_tally_dist(s, 1, s.match_length - MIN_MATCH, bflush); ***/
      bflush = trees._tr_tally(s, 1, s.match_length - MIN_MATCH);

      s.lookahead -= s.match_length;
      s.strstart += s.match_length;
      s.match_length = 0;
    } else {
      /* No match, output a literal byte */
      //Tracevv((stderr,"%c", s->window[s->strstart]));
      /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
      bflush = trees._tr_tally(s, 0, s.window[s.strstart]);

      s.lookahead--;
      s.strstart++;
    }
    if (bflush) {
      /*** FLUSH_BLOCK(s, 0); ***/
      flush_block_only(s, false);
      if (s.strm.avail_out === 0) {
        return BS_NEED_MORE;
      }
      /***/
    }
  }
  s.insert = 0;
  if (flush === Z_FINISH) {
    /*** FLUSH_BLOCK(s, 1); ***/
    flush_block_only(s, true);
    if (s.strm.avail_out === 0) {
      return BS_FINISH_STARTED;
    }
    /***/
    return BS_FINISH_DONE;
  }
  if (s.last_lit) {
    /*** FLUSH_BLOCK(s, 0); ***/
    flush_block_only(s, false);
    if (s.strm.avail_out === 0) {
      return BS_NEED_MORE;
    }
    /***/
  }
  return BS_BLOCK_DONE;
}

/* ===========================================================================
 * For Z_HUFFMAN_ONLY, do not look for matches.  Do not maintain a hash table.
 * (It will be regenerated if this run of deflate switches away from Huffman.)
 */
function deflate_huff(s, flush) {
  var bflush;             /* set if current block must be flushed */

  for (;;) {
    /* Make sure that we have a literal to write. */
    if (s.lookahead === 0) {
      fill_window(s);
      if (s.lookahead === 0) {
        if (flush === Z_NO_FLUSH) {
          return BS_NEED_MORE;
        }
        break;      /* flush the current block */
      }
    }

    /* Output a literal byte */
    s.match_length = 0;
    //Tracevv((stderr,"%c", s->window[s->strstart]));
    /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
    bflush = trees._tr_tally(s, 0, s.window[s.strstart]);
    s.lookahead--;
    s.strstart++;
    if (bflush) {
      /*** FLUSH_BLOCK(s, 0); ***/
      flush_block_only(s, false);
      if (s.strm.avail_out === 0) {
        return BS_NEED_MORE;
      }
      /***/
    }
  }
  s.insert = 0;
  if (flush === Z_FINISH) {
    /*** FLUSH_BLOCK(s, 1); ***/
    flush_block_only(s, true);
    if (s.strm.avail_out === 0) {
      return BS_FINISH_STARTED;
    }
    /***/
    return BS_FINISH_DONE;
  }
  if (s.last_lit) {
    /*** FLUSH_BLOCK(s, 0); ***/
    flush_block_only(s, false);
    if (s.strm.avail_out === 0) {
      return BS_NEED_MORE;
    }
    /***/
  }
  return BS_BLOCK_DONE;
}

/* Values for max_lazy_match, good_match and max_chain_length, depending on
 * the desired pack level (0..9). The values given below have been tuned to
 * exclude worst case performance for pathological files. Better values may be
 * found for specific files.
 */
function Config(good_length, max_lazy, nice_length, max_chain, func) {
  this.good_length = good_length;
  this.max_lazy = max_lazy;
  this.nice_length = nice_length;
  this.max_chain = max_chain;
  this.func = func;
}

var configuration_table;

configuration_table = [
  /*      good lazy nice chain */
  new Config(0, 0, 0, 0, deflate_stored),          /* 0 store only */
  new Config(4, 4, 8, 4, deflate_fast),            /* 1 max speed, no lazy matches */
  new Config(4, 5, 16, 8, deflate_fast),           /* 2 */
  new Config(4, 6, 32, 32, deflate_fast),          /* 3 */

  new Config(4, 4, 16, 16, deflate_slow),          /* 4 lazy matches */
  new Config(8, 16, 32, 32, deflate_slow),         /* 5 */
  new Config(8, 16, 128, 128, deflate_slow),       /* 6 */
  new Config(8, 32, 128, 256, deflate_slow),       /* 7 */
  new Config(32, 128, 258, 1024, deflate_slow),    /* 8 */
  new Config(32, 258, 258, 4096, deflate_slow)     /* 9 max compression */
];


/* ===========================================================================
 * Initialize the "longest match" routines for a new zlib stream
 */
function lm_init(s) {
  s.window_size = 2 * s.w_size;

  /*** CLEAR_HASH(s); ***/
  zero(s.head); // Fill with NIL (= 0);

  /* Set the default configuration parameters:
   */
  s.max_lazy_match = configuration_table[s.level].max_lazy;
  s.good_match = configuration_table[s.level].good_length;
  s.nice_match = configuration_table[s.level].nice_length;
  s.max_chain_length = configuration_table[s.level].max_chain;

  s.strstart = 0;
  s.block_start = 0;
  s.lookahead = 0;
  s.insert = 0;
  s.match_length = s.prev_length = MIN_MATCH - 1;
  s.match_available = 0;
  s.ins_h = 0;
}


function DeflateState() {
  this.strm = null;            /* pointer back to this zlib stream */
  this.status = 0;            /* as the name implies */
  this.pending_buf = null;      /* output still pending */
  this.pending_buf_size = 0;  /* size of pending_buf */
  this.pending_out = 0;       /* next pending byte to output to the stream */
  this.pending = 0;           /* nb of bytes in the pending buffer */
  this.wrap = 0;              /* bit 0 true for zlib, bit 1 true for gzip */
  this.gzhead = null;         /* gzip header information to write */
  this.gzindex = 0;           /* where in extra, name, or comment */
  this.method = Z_DEFLATED; /* can only be DEFLATED */
  this.last_flush = -1;   /* value of flush param for previous deflate call */

  this.w_size = 0;  /* LZ77 window size (32K by default) */
  this.w_bits = 0;  /* log2(w_size)  (8..16) */
  this.w_mask = 0;  /* w_size - 1 */

  this.window = null;
  /* Sliding window. Input bytes are read into the second half of the window,
   * and move to the first half later to keep a dictionary of at least wSize
   * bytes. With this organization, matches are limited to a distance of
   * wSize-MAX_MATCH bytes, but this ensures that IO is always
   * performed with a length multiple of the block size.
   */

  this.window_size = 0;
  /* Actual size of window: 2*wSize, except when the user input buffer
   * is directly used as sliding window.
   */

  this.prev = null;
  /* Link to older string with same hash index. To limit the size of this
   * array to 64K, this link is maintained only for the last 32K strings.
   * An index in this array is thus a window index modulo 32K.
   */

  this.head = null;   /* Heads of the hash chains or NIL. */

  this.ins_h = 0;       /* hash index of string to be inserted */
  this.hash_size = 0;   /* number of elements in hash table */
  this.hash_bits = 0;   /* log2(hash_size) */
  this.hash_mask = 0;   /* hash_size-1 */

  this.hash_shift = 0;
  /* Number of bits by which ins_h must be shifted at each input
   * step. It must be such that after MIN_MATCH steps, the oldest
   * byte no longer takes part in the hash key, that is:
   *   hash_shift * MIN_MATCH >= hash_bits
   */

  this.block_start = 0;
  /* Window position at the beginning of the current output block. Gets
   * negative when the window is moved backwards.
   */

  this.match_length = 0;      /* length of best match */
  this.prev_match = 0;        /* previous match */
  this.match_available = 0;   /* set if previous match exists */
  this.strstart = 0;          /* start of string to insert */
  this.match_start = 0;       /* start of matching string */
  this.lookahead = 0;         /* number of valid bytes ahead in window */

  this.prev_length = 0;
  /* Length of the best match at previous step. Matches not greater than this
   * are discarded. This is used in the lazy match evaluation.
   */

  this.max_chain_length = 0;
  /* To speed up deflation, hash chains are never searched beyond this
   * length.  A higher limit improves compression ratio but degrades the
   * speed.
   */

  this.max_lazy_match = 0;
  /* Attempt to find a better match only when the current match is strictly
   * smaller than this value. This mechanism is used only for compression
   * levels >= 4.
   */
  // That's alias to max_lazy_match, don't use directly
  //this.max_insert_length = 0;
  /* Insert new strings in the hash table only if the match length is not
   * greater than this length. This saves time but degrades compression.
   * max_insert_length is used only for compression levels <= 3.
   */

  this.level = 0;     /* compression level (1..9) */
  this.strategy = 0;  /* favor or force Huffman coding*/

  this.good_match = 0;
  /* Use a faster search when the previous match is longer than this */

  this.nice_match = 0; /* Stop searching when current match exceeds this */

              /* used by trees.c: */

  /* Didn't use ct_data typedef below to suppress compiler warning */

  // struct ct_data_s dyn_ltree[HEAP_SIZE];   /* literal and length tree */
  // struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */
  // struct ct_data_s bl_tree[2*BL_CODES+1];  /* Huffman tree for bit lengths */

  // Use flat array of DOUBLE size, with interleaved fata,
  // because JS does not support effective
  this.dyn_ltree  = new utils.Buf16(HEAP_SIZE * 2);
  this.dyn_dtree  = new utils.Buf16((2 * D_CODES + 1) * 2);
  this.bl_tree    = new utils.Buf16((2 * BL_CODES + 1) * 2);
  zero(this.dyn_ltree);
  zero(this.dyn_dtree);
  zero(this.bl_tree);

  this.l_desc   = null;         /* desc. for literal tree */
  this.d_desc   = null;         /* desc. for distance tree */
  this.bl_desc  = null;         /* desc. for bit length tree */

  //ush bl_count[MAX_BITS+1];
  this.bl_count = new utils.Buf16(MAX_BITS + 1);
  /* number of codes at each bit length for an optimal tree */

  //int heap[2*L_CODES+1];      /* heap used to build the Huffman trees */
  this.heap = new utils.Buf16(2 * L_CODES + 1);  /* heap used to build the Huffman trees */
  zero(this.heap);

  this.heap_len = 0;               /* number of elements in the heap */
  this.heap_max = 0;               /* element of largest frequency */
  /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used.
   * The same heap array is used to build all trees.
   */

  this.depth = new utils.Buf16(2 * L_CODES + 1); //uch depth[2*L_CODES+1];
  zero(this.depth);
  /* Depth of each subtree used as tie breaker for trees of equal frequency
   */

  this.l_buf = 0;          /* buffer index for literals or lengths */

  this.lit_bufsize = 0;
  /* Size of match buffer for literals/lengths.  There are 4 reasons for
   * limiting lit_bufsize to 64K:
   *   - frequencies can be kept in 16 bit counters
   *   - if compression is not successful for the first block, all input
   *     data is still in the window so we can still emit a stored block even
   *     when input comes from standard input.  (This can also be done for
   *     all blocks if lit_bufsize is not greater than 32K.)
   *   - if compression is not successful for a file smaller than 64K, we can
   *     even emit a stored file instead of a stored block (saving 5 bytes).
   *     This is applicable only for zip (not gzip or zlib).
   *   - creating new Huffman trees less frequently may not provide fast
   *     adaptation to changes in the input data statistics. (Take for
   *     example a binary file with poorly compressible code followed by
   *     a highly compressible string table.) Smaller buffer sizes give
   *     fast adaptation but have of course the overhead of transmitting
   *     trees more frequently.
   *   - I can't count above 4
   */

  this.last_lit = 0;      /* running index in l_buf */

  this.d_buf = 0;
  /* Buffer index for distances. To simplify the code, d_buf and l_buf have
   * the same number of elements. To use different lengths, an extra flag
   * array would be necessary.
   */

  this.opt_len = 0;       /* bit length of current block with optimal trees */
  this.static_len = 0;    /* bit length of current block with static trees */
  this.matches = 0;       /* number of string matches in current block */
  this.insert = 0;        /* bytes at end of window left to insert */


  this.bi_buf = 0;
  /* Output buffer. bits are inserted starting at the bottom (least
   * significant bits).
   */
  this.bi_valid = 0;
  /* Number of valid bits in bi_buf.  All bits above the last valid bit
   * are always zero.
   */

  // Used for window memory init. We safely ignore it for JS. That makes
  // sense only for pointers and memory check tools.
  //this.high_water = 0;
  /* High water mark offset in window for initialized bytes -- bytes above
   * this are set to zero in order to avoid memory check warnings when
   * longest match routines access bytes past the input.  This is then
   * updated to the new high water mark.
   */
}


function deflateResetKeep(strm) {
  var s;

  if (!strm || !strm.state) {
    return err(strm, Z_STREAM_ERROR);
  }

  strm.total_in = strm.total_out = 0;
  strm.data_type = Z_UNKNOWN;

  s = strm.state;
  s.pending = 0;
  s.pending_out = 0;

  if (s.wrap < 0) {
    s.wrap = -s.wrap;
    /* was made negative by deflate(..., Z_FINISH); */
  }
  s.status = (s.wrap ? INIT_STATE : BUSY_STATE);
  strm.adler = (s.wrap === 2) ?
    0  // crc32(0, Z_NULL, 0)
  :
    1; // adler32(0, Z_NULL, 0)
  s.last_flush = Z_NO_FLUSH;
  trees._tr_init(s);
  return Z_OK;
}


function deflateReset(strm) {
  var ret = deflateResetKeep(strm);
  if (ret === Z_OK) {
    lm_init(strm.state);
  }
  return ret;
}


function deflateSetHeader(strm, head) {
  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
  if (strm.state.wrap !== 2) { return Z_STREAM_ERROR; }
  strm.state.gzhead = head;
  return Z_OK;
}


function deflateInit2(strm, level, method, windowBits, memLevel, strategy) {
  if (!strm) { // === Z_NULL
    return Z_STREAM_ERROR;
  }
  var wrap = 1;

  if (level === Z_DEFAULT_COMPRESSION) {
    level = 6;
  }

  if (windowBits < 0) { /* suppress zlib wrapper */
    wrap = 0;
    windowBits = -windowBits;
  }

  else if (windowBits > 15) {
    wrap = 2;           /* write gzip wrapper instead */
    windowBits -= 16;
  }


  if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED ||
    windowBits < 8 || windowBits > 15 || level < 0 || level > 9 ||
    strategy < 0 || strategy > Z_FIXED) {
    return err(strm, Z_STREAM_ERROR);
  }


  if (windowBits === 8) {
    windowBits = 9;
  }
  /* until 256-byte window bug fixed */

  var s = new DeflateState();

  strm.state = s;
  s.strm = strm;

  s.wrap = wrap;
  s.gzhead = null;
  s.w_bits = windowBits;
  s.w_size = 1 << s.w_bits;
  s.w_mask = s.w_size - 1;

  s.hash_bits = memLevel + 7;
  s.hash_size = 1 << s.hash_bits;
  s.hash_mask = s.hash_size - 1;
  s.hash_shift = ~~((s.hash_bits + MIN_MATCH - 1) / MIN_MATCH);

  s.window = new utils.Buf8(s.w_size * 2);
  s.head = new utils.Buf16(s.hash_size);
  s.prev = new utils.Buf16(s.w_size);

  // Don't need mem init magic for JS.
  //s.high_water = 0;  /* nothing written to s->window yet */

  s.lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */

  s.pending_buf_size = s.lit_bufsize * 4;

  //overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2);
  //s->pending_buf = (uchf *) overlay;
  s.pending_buf = new utils.Buf8(s.pending_buf_size);

  // It is offset from `s.pending_buf` (size is `s.lit_bufsize * 2`)
  //s->d_buf = overlay + s->lit_bufsize/sizeof(ush);
  s.d_buf = 1 * s.lit_bufsize;

  //s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize;
  s.l_buf = (1 + 2) * s.lit_bufsize;

  s.level = level;
  s.strategy = strategy;
  s.method = method;

  return deflateReset(strm);
}

function deflateInit(strm, level) {
  return deflateInit2(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
}


function deflate(strm, flush) {
  var old_flush, s;
  var beg, val; // for gzip header write only

  if (!strm || !strm.state ||
    flush > Z_BLOCK || flush < 0) {
    return strm ? err(strm, Z_STREAM_ERROR) : Z_STREAM_ERROR;
  }

  s = strm.state;

  if (!strm.output ||
      (!strm.input && strm.avail_in !== 0) ||
      (s.status === FINISH_STATE && flush !== Z_FINISH)) {
    return err(strm, (strm.avail_out === 0) ? Z_BUF_ERROR : Z_STREAM_ERROR);
  }

  s.strm = strm; /* just in case */
  old_flush = s.last_flush;
  s.last_flush = flush;

  /* Write the header */
  if (s.status === INIT_STATE) {

    if (s.wrap === 2) { // GZIP header
      strm.adler = 0;  //crc32(0L, Z_NULL, 0);
      put_byte(s, 31);
      put_byte(s, 139);
      put_byte(s, 8);
      if (!s.gzhead) { // s->gzhead == Z_NULL
        put_byte(s, 0);
        put_byte(s, 0);
        put_byte(s, 0);
        put_byte(s, 0);
        put_byte(s, 0);
        put_byte(s, s.level === 9 ? 2 :
                    (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ?
                     4 : 0));
        put_byte(s, OS_CODE);
        s.status = BUSY_STATE;
      }
      else {
        put_byte(s, (s.gzhead.text ? 1 : 0) +
                    (s.gzhead.hcrc ? 2 : 0) +
                    (!s.gzhead.extra ? 0 : 4) +
                    (!s.gzhead.name ? 0 : 8) +
                    (!s.gzhead.comment ? 0 : 16)
                );
        put_byte(s, s.gzhead.time & 0xff);
        put_byte(s, (s.gzhead.time >> 8) & 0xff);
        put_byte(s, (s.gzhead.time >> 16) & 0xff);
        put_byte(s, (s.gzhead.time >> 24) & 0xff);
        put_byte(s, s.level === 9 ? 2 :
                    (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ?
                     4 : 0));
        put_byte(s, s.gzhead.os & 0xff);
        if (s.gzhead.extra && s.gzhead.extra.length) {
          put_byte(s, s.gzhead.extra.length & 0xff);
          put_byte(s, (s.gzhead.extra.length >> 8) & 0xff);
        }
        if (s.gzhead.hcrc) {
          strm.adler = crc32(strm.adler, s.pending_buf, s.pending, 0);
        }
        s.gzindex = 0;
        s.status = EXTRA_STATE;
      }
    }
    else // DEFLATE header
    {
      var header = (Z_DEFLATED + ((s.w_bits - 8) << 4)) << 8;
      var level_flags = -1;

      if (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2) {
        level_flags = 0;
      } else if (s.level < 6) {
        level_flags = 1;
      } else if (s.level === 6) {
        level_flags = 2;
      } else {
        level_flags = 3;
      }
      header |= (level_flags << 6);
      if (s.strstart !== 0) { header |= PRESET_DICT; }
      header += 31 - (header % 31);

      s.status = BUSY_STATE;
      putShortMSB(s, header);

      /* Save the adler32 of the preset dictionary: */
      if (s.strstart !== 0) {
        putShortMSB(s, strm.adler >>> 16);
        putShortMSB(s, strm.adler & 0xffff);
      }
      strm.adler = 1; // adler32(0L, Z_NULL, 0);
    }
  }

//#ifdef GZIP
  if (s.status === EXTRA_STATE) {
    if (s.gzhead.extra/* != Z_NULL*/) {
      beg = s.pending;  /* start of bytes to update crc */

      while (s.gzindex < (s.gzhead.extra.length & 0xffff)) {
        if (s.pending === s.pending_buf_size) {
          if (s.gzhead.hcrc && s.pending > beg) {
            strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
          }
          flush_pending(strm);
          beg = s.pending;
          if (s.pending === s.pending_buf_size) {
            break;
          }
        }
        put_byte(s, s.gzhead.extra[s.gzindex] & 0xff);
        s.gzindex++;
      }
      if (s.gzhead.hcrc && s.pending > beg) {
        strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
      }
      if (s.gzindex === s.gzhead.extra.length) {
        s.gzindex = 0;
        s.status = NAME_STATE;
      }
    }
    else {
      s.status = NAME_STATE;
    }
  }
  if (s.status === NAME_STATE) {
    if (s.gzhead.name/* != Z_NULL*/) {
      beg = s.pending;  /* start of bytes to update crc */
      //int val;

      do {
        if (s.pending === s.pending_buf_size) {
          if (s.gzhead.hcrc && s.pending > beg) {
            strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
          }
          flush_pending(strm);
          beg = s.pending;
          if (s.pending === s.pending_buf_size) {
            val = 1;
            break;
          }
        }
        // JS specific: little magic to add zero terminator to end of string
        if (s.gzindex < s.gzhead.name.length) {
          val = s.gzhead.name.charCodeAt(s.gzindex++) & 0xff;
        } else {
          val = 0;
        }
        put_byte(s, val);
      } while (val !== 0);

      if (s.gzhead.hcrc && s.pending > beg) {
        strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
      }
      if (val === 0) {
        s.gzindex = 0;
        s.status = COMMENT_STATE;
      }
    }
    else {
      s.status = COMMENT_STATE;
    }
  }
  if (s.status === COMMENT_STATE) {
    if (s.gzhead.comment/* != Z_NULL*/) {
      beg = s.pending;  /* start of bytes to update crc */
      //int val;

      do {
        if (s.pending === s.pending_buf_size) {
          if (s.gzhead.hcrc && s.pending > beg) {
            strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
          }
          flush_pending(strm);
          beg = s.pending;
          if (s.pending === s.pending_buf_size) {
            val = 1;
            break;
          }
        }
        // JS specific: little magic to add zero terminator to end of string
        if (s.gzindex < s.gzhead.comment.length) {
          val = s.gzhead.comment.charCodeAt(s.gzindex++) & 0xff;
        } else {
          val = 0;
        }
        put_byte(s, val);
      } while (val !== 0);

      if (s.gzhead.hcrc && s.pending > beg) {
        strm.adler = crc32(strm.adler, s.pending_buf, s.pending - beg, beg);
      }
      if (val === 0) {
        s.status = HCRC_STATE;
      }
    }
    else {
      s.status = HCRC_STATE;
    }
  }
  if (s.status === HCRC_STATE) {
    if (s.gzhead.hcrc) {
      if (s.pending + 2 > s.pending_buf_size) {
        flush_pending(strm);
      }
      if (s.pending + 2 <= s.pending_buf_size) {
        put_byte(s, strm.adler & 0xff);
        put_byte(s, (strm.adler >> 8) & 0xff);
        strm.adler = 0; //crc32(0L, Z_NULL, 0);
        s.status = BUSY_STATE;
      }
    }
    else {
      s.status = BUSY_STATE;
    }
  }
//#endif

  /* Flush as much pending output as possible */
  if (s.pending !== 0) {
    flush_pending(strm);
    if (strm.avail_out === 0) {
      /* Since avail_out is 0, deflate will be called again with
       * more output space, but possibly with both pending and
       * avail_in equal to zero. There won't be anything to do,
       * but this is not an error situation so make sure we
       * return OK instead of BUF_ERROR at next call of deflate:
       */
      s.last_flush = -1;
      return Z_OK;
    }

    /* Make sure there is something to do and avoid duplicate consecutive
     * flushes. For repeated and useless calls with Z_FINISH, we keep
     * returning Z_STREAM_END instead of Z_BUF_ERROR.
     */
  } else if (strm.avail_in === 0 && rank(flush) <= rank(old_flush) &&
    flush !== Z_FINISH) {
    return err(strm, Z_BUF_ERROR);
  }

  /* User must not provide more input after the first FINISH: */
  if (s.status === FINISH_STATE && strm.avail_in !== 0) {
    return err(strm, Z_BUF_ERROR);
  }

  /* Start a new block or continue the current one.
   */
  if (strm.avail_in !== 0 || s.lookahead !== 0 ||
    (flush !== Z_NO_FLUSH && s.status !== FINISH_STATE)) {
    var bstate = (s.strategy === Z_HUFFMAN_ONLY) ? deflate_huff(s, flush) :
      (s.strategy === Z_RLE ? deflate_rle(s, flush) :
        configuration_table[s.level].func(s, flush));

    if (bstate === BS_FINISH_STARTED || bstate === BS_FINISH_DONE) {
      s.status = FINISH_STATE;
    }
    if (bstate === BS_NEED_MORE || bstate === BS_FINISH_STARTED) {
      if (strm.avail_out === 0) {
        s.last_flush = -1;
        /* avoid BUF_ERROR next call, see above */
      }
      return Z_OK;
      /* If flush != Z_NO_FLUSH && avail_out == 0, the next call
       * of deflate should use the same flush parameter to make sure
       * that the flush is complete. So we don't have to output an
       * empty block here, this will be done at next call. This also
       * ensures that for a very small output buffer, we emit at most
       * one empty block.
       */
    }
    if (bstate === BS_BLOCK_DONE) {
      if (flush === Z_PARTIAL_FLUSH) {
        trees._tr_align(s);
      }
      else if (flush !== Z_BLOCK) { /* FULL_FLUSH or SYNC_FLUSH */

        trees._tr_stored_block(s, 0, 0, false);
        /* For a full flush, this empty block will be recognized
         * as a special marker by inflate_sync().
         */
        if (flush === Z_FULL_FLUSH) {
          /*** CLEAR_HASH(s); ***/             /* forget history */
          zero(s.head); // Fill with NIL (= 0);

          if (s.lookahead === 0) {
            s.strstart = 0;
            s.block_start = 0;
            s.insert = 0;
          }
        }
      }
      flush_pending(strm);
      if (strm.avail_out === 0) {
        s.last_flush = -1; /* avoid BUF_ERROR at next call, see above */
        return Z_OK;
      }
    }
  }
  //Assert(strm->avail_out > 0, "bug2");
  //if (strm.avail_out <= 0) { throw new Error("bug2");}

  if (flush !== Z_FINISH) { return Z_OK; }
  if (s.wrap <= 0) { return Z_STREAM_END; }

  /* Write the trailer */
  if (s.wrap === 2) {
    put_byte(s, strm.adler & 0xff);
    put_byte(s, (strm.adler >> 8) & 0xff);
    put_byte(s, (strm.adler >> 16) & 0xff);
    put_byte(s, (strm.adler >> 24) & 0xff);
    put_byte(s, strm.total_in & 0xff);
    put_byte(s, (strm.total_in >> 8) & 0xff);
    put_byte(s, (strm.total_in >> 16) & 0xff);
    put_byte(s, (strm.total_in >> 24) & 0xff);
  }
  else
  {
    putShortMSB(s, strm.adler >>> 16);
    putShortMSB(s, strm.adler & 0xffff);
  }

  flush_pending(strm);
  /* If avail_out is zero, the application will call deflate again
   * to flush the rest.
   */
  if (s.wrap > 0) { s.wrap = -s.wrap; }
  /* write the trailer only once! */
  return s.pending !== 0 ? Z_OK : Z_STREAM_END;
}

function deflateEnd(strm) {
  var status;

  if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) {
    return Z_STREAM_ERROR;
  }

  status = strm.state.status;
  if (status !== INIT_STATE &&
    status !== EXTRA_STATE &&
    status !== NAME_STATE &&
    status !== COMMENT_STATE &&
    status !== HCRC_STATE &&
    status !== BUSY_STATE &&
    status !== FINISH_STATE
  ) {
    return err(strm, Z_STREAM_ERROR);
  }

  strm.state = null;

  return status === BUSY_STATE ? err(strm, Z_DATA_ERROR) : Z_OK;
}


/* =========================================================================
 * Initializes the compression dictionary from the given byte
 * sequence without producing any compressed output.
 */
function deflateSetDictionary(strm, dictionary) {
  var dictLength = dictionary.length;

  var s;
  var str, n;
  var wrap;
  var avail;
  var next;
  var input;
  var tmpDict;

  if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) {
    return Z_STREAM_ERROR;
  }

  s = strm.state;
  wrap = s.wrap;

  if (wrap === 2 || (wrap === 1 && s.status !== INIT_STATE) || s.lookahead) {
    return Z_STREAM_ERROR;
  }

  /* when using zlib wrappers, compute Adler-32 for provided dictionary */
  if (wrap === 1) {
    /* adler32(strm->adler, dictionary, dictLength); */
    strm.adler = adler32(strm.adler, dictionary, dictLength, 0);
  }

  s.wrap = 0;   /* avoid computing Adler-32 in read_buf */

  /* if dictionary would fill window, just replace the history */
  if (dictLength >= s.w_size) {
    if (wrap === 0) {            /* already empty otherwise */
      /*** CLEAR_HASH(s); ***/
      zero(s.head); // Fill with NIL (= 0);
      s.strstart = 0;
      s.block_start = 0;
      s.insert = 0;
    }
    /* use the tail */
    // dictionary = dictionary.slice(dictLength - s.w_size);
    tmpDict = new utils.Buf8(s.w_size);
    utils.arraySet(tmpDict, dictionary, dictLength - s.w_size, s.w_size, 0);
    dictionary = tmpDict;
    dictLength = s.w_size;
  }
  /* insert dictionary into window and hash */
  avail = strm.avail_in;
  next = strm.next_in;
  input = strm.input;
  strm.avail_in = dictLength;
  strm.next_in = 0;
  strm.input = dictionary;
  fill_window(s);
  while (s.lookahead >= MIN_MATCH) {
    str = s.strstart;
    n = s.lookahead - (MIN_MATCH - 1);
    do {
      /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */
      s.ins_h = ((s.ins_h << s.hash_shift) ^ s.window[str + MIN_MATCH - 1]) & s.hash_mask;

      s.prev[str & s.w_mask] = s.head[s.ins_h];

      s.head[s.ins_h] = str;
      str++;
    } while (--n);
    s.strstart = str;
    s.lookahead = MIN_MATCH - 1;
    fill_window(s);
  }
  s.strstart += s.lookahead;
  s.block_start = s.strstart;
  s.insert = s.lookahead;
  s.lookahead = 0;
  s.match_length = s.prev_length = MIN_MATCH - 1;
  s.match_available = 0;
  strm.next_in = next;
  strm.input = input;
  strm.avail_in = avail;
  s.wrap = wrap;
  return Z_OK;
}


exports.deflateInit = deflateInit;
exports.deflateInit2 = deflateInit2;
exports.deflateReset = deflateReset;
exports.deflateResetKeep = deflateResetKeep;
exports.deflateSetHeader = deflateSetHeader;
exports.deflate = deflate;
exports.deflateEnd = deflateEnd;
exports.deflateSetDictionary = deflateSetDictionary;
exports.deflateInfo = 'pako deflate (from Nodeca project)';

/* Not implemented
exports.deflateBound = deflateBound;
exports.deflateCopy = deflateCopy;
exports.deflateParams = deflateParams;
exports.deflatePending = deflatePending;
exports.deflatePrime = deflatePrime;
exports.deflateTune = deflateTune;
*/

},{"../utils/common":3,"./adler32":5,"./crc32":7,"./messages":13,"./trees":14}],9:[function(require,module,exports){
'use strict';

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

function GZheader() {
  /* true if compressed data believed to be text */
  this.text       = 0;
  /* modification time */
  this.time       = 0;
  /* extra flags (not used when writing a gzip file) */
  this.xflags     = 0;
  /* operating system */
  this.os         = 0;
  /* pointer to extra field or Z_NULL if none */
  this.extra      = null;
  /* extra field length (valid if extra != Z_NULL) */
  this.extra_len  = 0; // Actually, we don't need it in JS,
                       // but leave for few code modifications

  //
  // Setup limits is not necessary because in js we should not preallocate memory
  // for inflate use constant limit in 65536 bytes
  //

  /* space at extra (only when reading header) */
  // this.extra_max  = 0;
  /* pointer to zero-terminated file name or Z_NULL */
  this.name       = '';
  /* space at name (only when reading header) */
  // this.name_max   = 0;
  /* pointer to zero-terminated comment or Z_NULL */
  this.comment    = '';
  /* space at comment (only when reading header) */
  // this.comm_max   = 0;
  /* true if there was or will be a header crc */
  this.hcrc       = 0;
  /* true when done reading gzip header (not used when writing a gzip file) */
  this.done       = false;
}

module.exports = GZheader;

},{}],10:[function(require,module,exports){
'use strict';

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

// See state defs from inflate.js
var BAD = 30;       /* got a data error -- remain here until reset */
var TYPE = 12;      /* i: waiting for type bits, including last-flag bit */

/*
   Decode literal, length, and distance codes and write out the resulting
   literal and match bytes until either not enough input or output is
   available, an end-of-block is encountered, or a data error is encountered.
   When large enough input and output buffers are supplied to inflate(), for
   example, a 16K input buffer and a 64K output buffer, more than 95% of the
   inflate execution time is spent in this routine.

   Entry assumptions:

        state.mode === LEN
        strm.avail_in >= 6
        strm.avail_out >= 258
        start >= strm.avail_out
        state.bits < 8

   On return, state.mode is one of:

        LEN -- ran out of enough output space or enough available input
        TYPE -- reached end of block code, inflate() to interpret next block
        BAD -- error in block data

   Notes:

    - The maximum input bits used by a length/distance pair is 15 bits for the
      length code, 5 bits for the length extra, 15 bits for the distance code,
      and 13 bits for the distance extra.  This totals 48 bits, or six bytes.
      Therefore if strm.avail_in >= 6, then there is enough input to avoid
      checking for available input while decoding.

    - The maximum bytes that a single length/distance pair can output is 258
      bytes, which is the maximum length that can be coded.  inflate_fast()
      requires strm.avail_out >= 258 for each loop to avoid checking for
      output space.
 */
module.exports = function inflate_fast(strm, start) {
  var state;
  var _in;                    /* local strm.input */
  var last;                   /* have enough input while in < last */
  var _out;                   /* local strm.output */
  var beg;                    /* inflate()'s initial strm.output */
  var end;                    /* while out < end, enough space available */
//#ifdef INFLATE_STRICT
  var dmax;                   /* maximum distance from zlib header */
//#endif
  var wsize;                  /* window size or zero if not using window */
  var whave;                  /* valid bytes in the window */
  var wnext;                  /* window write index */
  // Use `s_window` instead `window`, avoid conflict with instrumentation tools
  var s_window;               /* allocated sliding window, if wsize != 0 */
  var hold;                   /* local strm.hold */
  var bits;                   /* local strm.bits */
  var lcode;                  /* local strm.lencode */
  var dcode;                  /* local strm.distcode */
  var lmask;                  /* mask for first level of length codes */
  var dmask;                  /* mask for first level of distance codes */
  var here;                   /* retrieved table entry */
  var op;                     /* code bits, operation, extra bits, or */
                              /*  window position, window bytes to copy */
  var len;                    /* match length, unused bytes */
  var dist;                   /* match distance */
  var from;                   /* where to copy match from */
  var from_source;


  var input, output; // JS specific, because we have no pointers

  /* copy state to local variables */
  state = strm.state;
  //here = state.here;
  _in = strm.next_in;
  input = strm.input;
  last = _in + (strm.avail_in - 5);
  _out = strm.next_out;
  output = strm.output;
  beg = _out - (start - strm.avail_out);
  end = _out + (strm.avail_out - 257);
//#ifdef INFLATE_STRICT
  dmax = state.dmax;
//#endif
  wsize = state.wsize;
  whave = state.whave;
  wnext = state.wnext;
  s_window = state.window;
  hold = state.hold;
  bits = state.bits;
  lcode = state.lencode;
  dcode = state.distcode;
  lmask = (1 << state.lenbits) - 1;
  dmask = (1 << state.distbits) - 1;


  /* decode literals and length/distances until end-of-block or not enough
     input data or output space */

  top:
  do {
    if (bits < 15) {
      hold += input[_in++] << bits;
      bits += 8;
      hold += input[_in++] << bits;
      bits += 8;
    }

    here = lcode[hold & lmask];

    dolen:
    for (;;) { // Goto emulation
      op = here >>> 24/*here.bits*/;
      hold >>>= op;
      bits -= op;
      op = (here >>> 16) & 0xff/*here.op*/;
      if (op === 0) {                          /* literal */
        //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
        //        "inflate:         literal '%c'\n" :
        //        "inflate:         literal 0x%02x\n", here.val));
        output[_out++] = here & 0xffff/*here.val*/;
      }
      else if (op & 16) {                     /* length base */
        len = here & 0xffff/*here.val*/;
        op &= 15;                           /* number of extra bits */
        if (op) {
          if (bits < op) {
            hold += input[_in++] << bits;
            bits += 8;
          }
          len += hold & ((1 << op) - 1);
          hold >>>= op;
          bits -= op;
        }
        //Tracevv((stderr, "inflate:         length %u\n", len));
        if (bits < 15) {
          hold += input[_in++] << bits;
          bits += 8;
          hold += input[_in++] << bits;
          bits += 8;
        }
        here = dcode[hold & dmask];

        dodist:
        for (;;) { // goto emulation
          op = here >>> 24/*here.bits*/;
          hold >>>= op;
          bits -= op;
          op = (here >>> 16) & 0xff/*here.op*/;

          if (op & 16) {                      /* distance base */
            dist = here & 0xffff/*here.val*/;
            op &= 15;                       /* number of extra bits */
            if (bits < op) {
              hold += input[_in++] << bits;
              bits += 8;
              if (bits < op) {
                hold += input[_in++] << bits;
                bits += 8;
              }
            }
            dist += hold & ((1 << op) - 1);
//#ifdef INFLATE_STRICT
            if (dist > dmax) {
              strm.msg = 'invalid distance too far back';
              state.mode = BAD;
              break top;
            }
//#endif
            hold >>>= op;
            bits -= op;
            //Tracevv((stderr, "inflate:         distance %u\n", dist));
            op = _out - beg;                /* max distance in output */
            if (dist > op) {                /* see if copy from window */
              op = dist - op;               /* distance back in window */
              if (op > whave) {
                if (state.sane) {
                  strm.msg = 'invalid distance too far back';
                  state.mode = BAD;
                  break top;
                }

// (!) This block is disabled in zlib defaults,
// don't enable it for binary compatibility
//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
//                if (len <= op - whave) {
//                  do {
//                    output[_out++] = 0;
//                  } while (--len);
//                  continue top;
//                }
//                len -= op - whave;
//                do {
//                  output[_out++] = 0;
//                } while (--op > whave);
//                if (op === 0) {
//                  from = _out - dist;
//                  do {
//                    output[_out++] = output[from++];
//                  } while (--len);
//                  continue top;
//                }
//#endif
              }
              from = 0; // window index
              from_source = s_window;
              if (wnext === 0) {           /* very common case */
                from += wsize - op;
                if (op < len) {         /* some from window */
                  len -= op;
                  do {
                    output[_out++] = s_window[from++];
                  } while (--op);
                  from = _out - dist;  /* rest from output */
                  from_source = output;
                }
              }
              else if (wnext < op) {      /* wrap around window */
                from += wsize + wnext - op;
                op -= wnext;
                if (op < len) {         /* some from end of window */
                  len -= op;
                  do {
                    output[_out++] = s_window[from++];
                  } while (--op);
                  from = 0;
                  if (wnext < len) {  /* some from start of window */
                    op = wnext;
                    len -= op;
                    do {
                      output[_out++] = s_window[from++];
                    } while (--op);
                    from = _out - dist;      /* rest from output */
                    from_source = output;
                  }
                }
              }
              else {                      /* contiguous in window */
                from += wnext - op;
                if (op < len) {         /* some from window */
                  len -= op;
                  do {
                    output[_out++] = s_window[from++];
                  } while (--op);
                  from = _out - dist;  /* rest from output */
                  from_source = output;
                }
              }
              while (len > 2) {
                output[_out++] = from_source[from++];
                output[_out++] = from_source[from++];
                output[_out++] = from_source[from++];
                len -= 3;
              }
              if (len) {
                output[_out++] = from_source[from++];
                if (len > 1) {
                  output[_out++] = from_source[from++];
                }
              }
            }
            else {
              from = _out - dist;          /* copy direct from output */
              do {                        /* minimum length is three */
                output[_out++] = output[from++];
                output[_out++] = output[from++];
                output[_out++] = output[from++];
                len -= 3;
              } while (len > 2);
              if (len) {
                output[_out++] = output[from++];
                if (len > 1) {
                  output[_out++] = output[from++];
                }
              }
            }
          }
          else if ((op & 64) === 0) {          /* 2nd level distance code */
            here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
            continue dodist;
          }
          else {
            strm.msg = 'invalid distance code';
            state.mode = BAD;
            break top;
          }

          break; // need to emulate goto via "continue"
        }
      }
      else if ((op & 64) === 0) {              /* 2nd level length code */
        here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
        continue dolen;
      }
      else if (op & 32) {                     /* end-of-block */
        //Tracevv((stderr, "inflate:         end of block\n"));
        state.mode = TYPE;
        break top;
      }
      else {
        strm.msg = 'invalid literal/length code';
        state.mode = BAD;
        break top;
      }

      break; // need to emulate goto via "continue"
    }
  } while (_in < last && _out < end);

  /* return unused bytes (on entry, bits < 8, so in won't go too far back) */
  len = bits >> 3;
  _in -= len;
  bits -= len << 3;
  hold &= (1 << bits) - 1;

  /* update state and return */
  strm.next_in = _in;
  strm.next_out = _out;
  strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last));
  strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end));
  state.hold = hold;
  state.bits = bits;
  return;
};

},{}],11:[function(require,module,exports){
'use strict';

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

var utils         = require('../utils/common');
var adler32       = require('./adler32');
var crc32         = require('./crc32');
var inflate_fast  = require('./inffast');
var inflate_table = require('./inftrees');

var CODES = 0;
var LENS = 1;
var DISTS = 2;

/* Public constants ==========================================================*/
/* ===========================================================================*/


/* Allowed flush values; see deflate() and inflate() below for details */
//var Z_NO_FLUSH      = 0;
//var Z_PARTIAL_FLUSH = 1;
//var Z_SYNC_FLUSH    = 2;
//var Z_FULL_FLUSH    = 3;
var Z_FINISH        = 4;
var Z_BLOCK         = 5;
var Z_TREES         = 6;


/* Return codes for the compression/decompression functions. Negative values
 * are errors, positive values are used for special but normal events.
 */
var Z_OK            = 0;
var Z_STREAM_END    = 1;
var Z_NEED_DICT     = 2;
//var Z_ERRNO         = -1;
var Z_STREAM_ERROR  = -2;
var Z_DATA_ERROR    = -3;
var Z_MEM_ERROR     = -4;
var Z_BUF_ERROR     = -5;
//var Z_VERSION_ERROR = -6;

/* The deflate compression method */
var Z_DEFLATED  = 8;


/* STATES ====================================================================*/
/* ===========================================================================*/


var    HEAD = 1;       /* i: waiting for magic header */
var    FLAGS = 2;      /* i: waiting for method and flags (gzip) */
var    TIME = 3;       /* i: waiting for modification time (gzip) */
var    OS = 4;         /* i: waiting for extra flags and operating system (gzip) */
var    EXLEN = 5;      /* i: waiting for extra length (gzip) */
var    EXTRA = 6;      /* i: waiting for extra bytes (gzip) */
var    NAME = 7;       /* i: waiting for end of file name (gzip) */
var    COMMENT = 8;    /* i: waiting for end of comment (gzip) */
var    HCRC = 9;       /* i: waiting for header crc (gzip) */
var    DICTID = 10;    /* i: waiting for dictionary check value */
var    DICT = 11;      /* waiting for inflateSetDictionary() call */
var        TYPE = 12;      /* i: waiting for type bits, including last-flag bit */
var        TYPEDO = 13;    /* i: same, but skip check to exit inflate on new block */
var        STORED = 14;    /* i: waiting for stored size (length and complement) */
var        COPY_ = 15;     /* i/o: same as COPY below, but only first time in */
var        COPY = 16;      /* i/o: waiting for input or output to copy stored block */
var        TABLE = 17;     /* i: waiting for dynamic block table lengths */
var        LENLENS = 18;   /* i: waiting for code length code lengths */
var        CODELENS = 19;  /* i: waiting for length/lit and distance code lengths */
var            LEN_ = 20;      /* i: same as LEN below, but only first time in */
var            LEN = 21;       /* i: waiting for length/lit/eob code */
var            LENEXT = 22;    /* i: waiting for length extra bits */
var            DIST = 23;      /* i: waiting for distance code */
var            DISTEXT = 24;   /* i: waiting for distance extra bits */
var            MATCH = 25;     /* o: waiting for output space to copy string */
var            LIT = 26;       /* o: waiting for output space to write literal */
var    CHECK = 27;     /* i: waiting for 32-bit check value */
var    LENGTH = 28;    /* i: waiting for 32-bit length (gzip) */
var    DONE = 29;      /* finished check, done -- remain here until reset */
var    BAD = 30;       /* got a data error -- remain here until reset */
var    MEM = 31;       /* got an inflate() memory error -- remain here until reset */
var    SYNC = 32;      /* looking for synchronization bytes to restart inflate() */

/* ===========================================================================*/



var ENOUGH_LENS = 852;
var ENOUGH_DISTS = 592;
//var ENOUGH =  (ENOUGH_LENS+ENOUGH_DISTS);

var MAX_WBITS = 15;
/* 32K LZ77 window */
var DEF_WBITS = MAX_WBITS;


function zswap32(q) {
  return  (((q >>> 24) & 0xff) +
          ((q >>> 8) & 0xff00) +
          ((q & 0xff00) << 8) +
          ((q & 0xff) << 24));
}


function InflateState() {
  this.mode = 0;             /* current inflate mode */
  this.last = false;          /* true if processing last block */
  this.wrap = 0;              /* bit 0 true for zlib, bit 1 true for gzip */
  this.havedict = false;      /* true if dictionary provided */
  this.flags = 0;             /* gzip header method and flags (0 if zlib) */
  this.dmax = 0;              /* zlib header max distance (INFLATE_STRICT) */
  this.check = 0;             /* protected copy of check value */
  this.total = 0;             /* protected copy of output count */
  // TODO: may be {}
  this.head = null;           /* where to save gzip header information */

  /* sliding window */
  this.wbits = 0;             /* log base 2 of requested window size */
  this.wsize = 0;             /* window size or zero if not using window */
  this.whave = 0;             /* valid bytes in the window */
  this.wnext = 0;             /* window write index */
  this.window = null;         /* allocated sliding window, if needed */

  /* bit accumulator */
  this.hold = 0;              /* input bit accumulator */
  this.bits = 0;              /* number of bits in "in" */

  /* for string and stored block copying */
  this.length = 0;            /* literal or length of data to copy */
  this.offset = 0;            /* distance back to copy string from */

  /* for table and code decoding */
  this.extra = 0;             /* extra bits needed */

  /* fixed and dynamic code tables */
  this.lencode = null;          /* starting table for length/literal codes */
  this.distcode = null;         /* starting table for distance codes */
  this.lenbits = 0;           /* index bits for lencode */
  this.distbits = 0;          /* index bits for distcode */

  /* dynamic table building */
  this.ncode = 0;             /* number of code length code lengths */
  this.nlen = 0;              /* number of length code lengths */
  this.ndist = 0;             /* number of distance code lengths */
  this.have = 0;              /* number of code lengths in lens[] */
  this.next = null;              /* next available space in codes[] */

  this.lens = new utils.Buf16(320); /* temporary storage for code lengths */
  this.work = new utils.Buf16(288); /* work area for code table building */

  /*
   because we don't have pointers in js, we use lencode and distcode directly
   as buffers so we don't need codes
  */
  //this.codes = new utils.Buf32(ENOUGH);       /* space for code tables */
  this.lendyn = null;              /* dynamic table for length/literal codes (JS specific) */
  this.distdyn = null;             /* dynamic table for distance codes (JS specific) */
  this.sane = 0;                   /* if false, allow invalid distance too far */
  this.back = 0;                   /* bits back of last unprocessed length/lit */
  this.was = 0;                    /* initial length of match */
}

function inflateResetKeep(strm) {
  var state;

  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
  state = strm.state;
  strm.total_in = strm.total_out = state.total = 0;
  strm.msg = ''; /*Z_NULL*/
  if (state.wrap) {       /* to support ill-conceived Java test suite */
    strm.adler = state.wrap & 1;
  }
  state.mode = HEAD;
  state.last = 0;
  state.havedict = 0;
  state.dmax = 32768;
  state.head = null/*Z_NULL*/;
  state.hold = 0;
  state.bits = 0;
  //state.lencode = state.distcode = state.next = state.codes;
  state.lencode = state.lendyn = new utils.Buf32(ENOUGH_LENS);
  state.distcode = state.distdyn = new utils.Buf32(ENOUGH_DISTS);

  state.sane = 1;
  state.back = -1;
  //Tracev((stderr, "inflate: reset\n"));
  return Z_OK;
}

function inflateReset(strm) {
  var state;

  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
  state = strm.state;
  state.wsize = 0;
  state.whave = 0;
  state.wnext = 0;
  return inflateResetKeep(strm);

}

function inflateReset2(strm, windowBits) {
  var wrap;
  var state;

  /* get the state */
  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
  state = strm.state;

  /* extract wrap request from windowBits parameter */
  if (windowBits < 0) {
    wrap = 0;
    windowBits = -windowBits;
  }
  else {
    wrap = (windowBits >> 4) + 1;
    if (windowBits < 48) {
      windowBits &= 15;
    }
  }

  /* set number of window bits, free window if different */
  if (windowBits && (windowBits < 8 || windowBits > 15)) {
    return Z_STREAM_ERROR;
  }
  if (state.window !== null && state.wbits !== windowBits) {
    state.window = null;
  }

  /* update state and reset the rest of it */
  state.wrap = wrap;
  state.wbits = windowBits;
  return inflateReset(strm);
}

function inflateInit2(strm, windowBits) {
  var ret;
  var state;

  if (!strm) { return Z_STREAM_ERROR; }
  //strm.msg = Z_NULL;                 /* in case we return an error */

  state = new InflateState();

  //if (state === Z_NULL) return Z_MEM_ERROR;
  //Tracev((stderr, "inflate: allocated\n"));
  strm.state = state;
  state.window = null/*Z_NULL*/;
  ret = inflateReset2(strm, windowBits);
  if (ret !== Z_OK) {
    strm.state = null/*Z_NULL*/;
  }
  return ret;
}

function inflateInit(strm) {
  return inflateInit2(strm, DEF_WBITS);
}


/*
 Return state with length and distance decoding tables and index sizes set to
 fixed code decoding.  Normally this returns fixed tables from inffixed.h.
 If BUILDFIXED is defined, then instead this routine builds the tables the
 first time it's called, and returns those tables the first time and
 thereafter.  This reduces the size of the code by about 2K bytes, in
 exchange for a little execution time.  However, BUILDFIXED should not be
 used for threaded applications, since the rewriting of the tables and virgin
 may not be thread-safe.
 */
var virgin = true;

var lenfix, distfix; // We have no pointers in JS, so keep tables separate

function fixedtables(state) {
  /* build fixed huffman tables if first call (may not be thread safe) */
  if (virgin) {
    var sym;

    lenfix = new utils.Buf32(512);
    distfix = new utils.Buf32(32);

    /* literal/length table */
    sym = 0;
    while (sym < 144) { state.lens[sym++] = 8; }
    while (sym < 256) { state.lens[sym++] = 9; }
    while (sym < 280) { state.lens[sym++] = 7; }
    while (sym < 288) { state.lens[sym++] = 8; }

    inflate_table(LENS,  state.lens, 0, 288, lenfix,   0, state.work, { bits: 9 });

    /* distance table */
    sym = 0;
    while (sym < 32) { state.lens[sym++] = 5; }

    inflate_table(DISTS, state.lens, 0, 32,   distfix, 0, state.work, { bits: 5 });

    /* do this just once */
    virgin = false;
  }

  state.lencode = lenfix;
  state.lenbits = 9;
  state.distcode = distfix;
  state.distbits = 5;
}


/*
 Update the window with the last wsize (normally 32K) bytes written before
 returning.  If window does not exist yet, create it.  This is only called
 when a window is already in use, or when output has been written during this
 inflate call, but the end of the deflate stream has not been reached yet.
 It is also called to create a window for dictionary data when a dictionary
 is loaded.

 Providing output buffers larger than 32K to inflate() should provide a speed
 advantage, since only the last 32K of output is copied to the sliding window
 upon return from inflate(), and since all distances after the first 32K of
 output will fall in the output data, making match copies simpler and faster.
 The advantage may be dependent on the size of the processor's data caches.
 */
function updatewindow(strm, src, end, copy) {
  var dist;
  var state = strm.state;

  /* if it hasn't been done already, allocate space for the window */
  if (state.window === null) {
    state.wsize = 1 << state.wbits;
    state.wnext = 0;
    state.whave = 0;

    state.window = new utils.Buf8(state.wsize);
  }

  /* copy state->wsize or less output bytes into the circular window */
  if (copy >= state.wsize) {
    utils.arraySet(state.window, src, end - state.wsize, state.wsize, 0);
    state.wnext = 0;
    state.whave = state.wsize;
  }
  else {
    dist = state.wsize - state.wnext;
    if (dist > copy) {
      dist = copy;
    }
    //zmemcpy(state->window + state->wnext, end - copy, dist);
    utils.arraySet(state.window, src, end - copy, dist, state.wnext);
    copy -= dist;
    if (copy) {
      //zmemcpy(state->window, end - copy, copy);
      utils.arraySet(state.window, src, end - copy, copy, 0);
      state.wnext = copy;
      state.whave = state.wsize;
    }
    else {
      state.wnext += dist;
      if (state.wnext === state.wsize) { state.wnext = 0; }
      if (state.whave < state.wsize) { state.whave += dist; }
    }
  }
  return 0;
}

function inflate(strm, flush) {
  var state;
  var input, output;          // input/output buffers
  var next;                   /* next input INDEX */
  var put;                    /* next output INDEX */
  var have, left;             /* available input and output */
  var hold;                   /* bit buffer */
  var bits;                   /* bits in bit buffer */
  var _in, _out;              /* save starting available input and output */
  var copy;                   /* number of stored or match bytes to copy */
  var from;                   /* where to copy match bytes from */
  var from_source;
  var here = 0;               /* current decoding table entry */
  var here_bits, here_op, here_val; // paked "here" denormalized (JS specific)
  //var last;                   /* parent table entry */
  var last_bits, last_op, last_val; // paked "last" denormalized (JS specific)
  var len;                    /* length to copy for repeats, bits to drop */
  var ret;                    /* return code */
  var hbuf = new utils.Buf8(4);    /* buffer for gzip header crc calculation */
  var opts;

  var n; // temporary var for NEED_BITS

  var order = /* permutation of code lengths */
    [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ];


  if (!strm || !strm.state || !strm.output ||
      (!strm.input && strm.avail_in !== 0)) {
    return Z_STREAM_ERROR;
  }

  state = strm.state;
  if (state.mode === TYPE) { state.mode = TYPEDO; }    /* skip check */


  //--- LOAD() ---
  put = strm.next_out;
  output = strm.output;
  left = strm.avail_out;
  next = strm.next_in;
  input = strm.input;
  have = strm.avail_in;
  hold = state.hold;
  bits = state.bits;
  //---

  _in = have;
  _out = left;
  ret = Z_OK;

  inf_leave: // goto emulation
  for (;;) {
    switch (state.mode) {
      case HEAD:
        if (state.wrap === 0) {
          state.mode = TYPEDO;
          break;
        }
        //=== NEEDBITS(16);
        while (bits < 16) {
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
        }
        //===//
        if ((state.wrap & 2) && hold === 0x8b1f) {  /* gzip header */
          state.check = 0/*crc32(0L, Z_NULL, 0)*/;
          //=== CRC2(state.check, hold);
          hbuf[0] = hold & 0xff;
          hbuf[1] = (hold >>> 8) & 0xff;
          state.check = crc32(state.check, hbuf, 2, 0);
          //===//

          //=== INITBITS();
          hold = 0;
          bits = 0;
          //===//
          state.mode = FLAGS;
          break;
        }
        state.flags = 0;           /* expect zlib header */
        if (state.head) {
          state.head.done = false;
        }
        if (!(state.wrap & 1) ||   /* check if zlib header allowed */
          (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) {
          strm.msg = 'incorrect header check';
          state.mode = BAD;
          break;
        }
        if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED) {
          strm.msg = 'unknown compression method';
          state.mode = BAD;
          break;
        }
        //--- DROPBITS(4) ---//
        hold >>>= 4;
        bits -= 4;
        //---//
        len = (hold & 0x0f)/*BITS(4)*/ + 8;
        if (state.wbits === 0) {
          state.wbits = len;
        }
        else if (len > state.wbits) {
          strm.msg = 'invalid window size';
          state.mode = BAD;
          break;
        }
        state.dmax = 1 << len;
        //Tracev((stderr, "inflate:   zlib header ok\n"));
        strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/;
        state.mode = hold & 0x200 ? DICTID : TYPE;
        //=== INITBITS();
        hold = 0;
        bits = 0;
        //===//
        break;
      case FLAGS:
        //=== NEEDBITS(16); */
        while (bits < 16) {
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
        }
        //===//
        state.flags = hold;
        if ((state.flags & 0xff) !== Z_DEFLATED) {
          strm.msg = 'unknown compression method';
          state.mode = BAD;
          break;
        }
        if (state.flags & 0xe000) {
          strm.msg = 'unknown header flags set';
          state.mode = BAD;
          break;
        }
        if (state.head) {
          state.head.text = ((hold >> 8) & 1);
        }
        if (state.flags & 0x0200) {
          //=== CRC2(state.check, hold);
          hbuf[0] = hold & 0xff;
          hbuf[1] = (hold >>> 8) & 0xff;
          state.check = crc32(state.check, hbuf, 2, 0);
          //===//
        }
        //=== INITBITS();
        hold = 0;
        bits = 0;
        //===//
        state.mode = TIME;
        /* falls through */
      case TIME:
        //=== NEEDBITS(32); */
        while (bits < 32) {
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
        }
        //===//
        if (state.head) {
          state.head.time = hold;
        }
        if (state.flags & 0x0200) {
          //=== CRC4(state.check, hold)
          hbuf[0] = hold & 0xff;
          hbuf[1] = (hold >>> 8) & 0xff;
          hbuf[2] = (hold >>> 16) & 0xff;
          hbuf[3] = (hold >>> 24) & 0xff;
          state.check = crc32(state.check, hbuf, 4, 0);
          //===
        }
        //=== INITBITS();
        hold = 0;
        bits = 0;
        //===//
        state.mode = OS;
        /* falls through */
      case OS:
        //=== NEEDBITS(16); */
        while (bits < 16) {
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
        }
        //===//
        if (state.head) {
          state.head.xflags = (hold & 0xff);
          state.head.os = (hold >> 8);
        }
        if (state.flags & 0x0200) {
          //=== CRC2(state.check, hold);
          hbuf[0] = hold & 0xff;
          hbuf[1] = (hold >>> 8) & 0xff;
          state.check = crc32(state.check, hbuf, 2, 0);
          //===//
        }
        //=== INITBITS();
        hold = 0;
        bits = 0;
        //===//
        state.mode = EXLEN;
        /* falls through */
      case EXLEN:
        if (state.flags & 0x0400) {
          //=== NEEDBITS(16); */
          while (bits < 16) {
            if (have === 0) { break inf_leave; }
            have--;
            hold += input[next++] << bits;
            bits += 8;
          }
          //===//
          state.length = hold;
          if (state.head) {
            state.head.extra_len = hold;
          }
          if (state.flags & 0x0200) {
            //=== CRC2(state.check, hold);
            hbuf[0] = hold & 0xff;
            hbuf[1] = (hold >>> 8) & 0xff;
            state.check = crc32(state.check, hbuf, 2, 0);
            //===//
          }
          //=== INITBITS();
          hold = 0;
          bits = 0;
          //===//
        }
        else if (state.head) {
          state.head.extra = null/*Z_NULL*/;
        }
        state.mode = EXTRA;
        /* falls through */
      case EXTRA:
        if (state.flags & 0x0400) {
          copy = state.length;
          if (copy > have) { copy = have; }
          if (copy) {
            if (state.head) {
              len = state.head.extra_len - state.length;
              if (!state.head.extra) {
                // Use untyped array for more convenient processing later
                state.head.extra = new Array(state.head.extra_len);
              }
              utils.arraySet(
                state.head.extra,
                input,
                next,
                // extra field is limited to 65536 bytes
                // - no need for additional size check
                copy,
                /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/
                len
              );
              //zmemcpy(state.head.extra + len, next,
              //        len + copy > state.head.extra_max ?
              //        state.head.extra_max - len : copy);
            }
            if (state.flags & 0x0200) {
              state.check = crc32(state.check, input, copy, next);
            }
            have -= copy;
            next += copy;
            state.length -= copy;
          }
          if (state.length) { break inf_leave; }
        }
        state.length = 0;
        state.mode = NAME;
        /* falls through */
      case NAME:
        if (state.flags & 0x0800) {
          if (have === 0) { break inf_leave; }
          copy = 0;
          do {
            // TODO: 2 or 1 bytes?
            len = input[next + copy++];
            /* use constant limit because in js we should not preallocate memory */
            if (state.head && len &&
                (state.length < 65536 /*state.head.name_max*/)) {
              state.head.name += String.fromCharCode(len);
            }
          } while (len && copy < have);

          if (state.flags & 0x0200) {
            state.check = crc32(state.check, input, copy, next);
          }
          have -= copy;
          next += copy;
          if (len) { break inf_leave; }
        }
        else if (state.head) {
          state.head.name = null;
        }
        state.length = 0;
        state.mode = COMMENT;
        /* falls through */
      case COMMENT:
        if (state.flags & 0x1000) {
          if (have === 0) { break inf_leave; }
          copy = 0;
          do {
            len = input[next + copy++];
            /* use constant limit because in js we should not preallocate memory */
            if (state.head && len &&
                (state.length < 65536 /*state.head.comm_max*/)) {
              state.head.comment += String.fromCharCode(len);
            }
          } while (len && copy < have);
          if (state.flags & 0x0200) {
            state.check = crc32(state.check, input, copy, next);
          }
          have -= copy;
          next += copy;
          if (len) { break inf_leave; }
        }
        else if (state.head) {
          state.head.comment = null;
        }
        state.mode = HCRC;
        /* falls through */
      case HCRC:
        if (state.flags & 0x0200) {
          //=== NEEDBITS(16); */
          while (bits < 16) {
            if (have === 0) { break inf_leave; }
            have--;
            hold += input[next++] << bits;
            bits += 8;
          }
          //===//
          if (hold !== (state.check & 0xffff)) {
            strm.msg = 'header crc mismatch';
            state.mode = BAD;
            break;
          }
          //=== INITBITS();
          hold = 0;
          bits = 0;
          //===//
        }
        if (state.head) {
          state.head.hcrc = ((state.flags >> 9) & 1);
          state.head.done = true;
        }
        strm.adler = state.check = 0;
        state.mode = TYPE;
        break;
      case DICTID:
        //=== NEEDBITS(32); */
        while (bits < 32) {
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
        }
        //===//
        strm.adler = state.check = zswap32(hold);
        //=== INITBITS();
        hold = 0;
        bits = 0;
        //===//
        state.mode = DICT;
        /* falls through */
      case DICT:
        if (state.havedict === 0) {
          //--- RESTORE() ---
          strm.next_out = put;
          strm.avail_out = left;
          strm.next_in = next;
          strm.avail_in = have;
          state.hold = hold;
          state.bits = bits;
          //---
          return Z_NEED_DICT;
        }
        strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/;
        state.mode = TYPE;
        /* falls through */
      case TYPE:
        if (flush === Z_BLOCK || flush === Z_TREES) { break inf_leave; }
        /* falls through */
      case TYPEDO:
        if (state.last) {
          //--- BYTEBITS() ---//
          hold >>>= bits & 7;
          bits -= bits & 7;
          //---//
          state.mode = CHECK;
          break;
        }
        //=== NEEDBITS(3); */
        while (bits < 3) {
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
        }
        //===//
        state.last = (hold & 0x01)/*BITS(1)*/;
        //--- DROPBITS(1) ---//
        hold >>>= 1;
        bits -= 1;
        //---//

        switch ((hold & 0x03)/*BITS(2)*/) {
          case 0:                             /* stored block */
            //Tracev((stderr, "inflate:     stored block%s\n",
            //        state.last ? " (last)" : ""));
            state.mode = STORED;
            break;
          case 1:                             /* fixed block */
            fixedtables(state);
            //Tracev((stderr, "inflate:     fixed codes block%s\n",
            //        state.last ? " (last)" : ""));
            state.mode = LEN_;             /* decode codes */
            if (flush === Z_TREES) {
              //--- DROPBITS(2) ---//
              hold >>>= 2;
              bits -= 2;
              //---//
              break inf_leave;
            }
            break;
          case 2:                             /* dynamic block */
            //Tracev((stderr, "inflate:     dynamic codes block%s\n",
            //        state.last ? " (last)" : ""));
            state.mode = TABLE;
            break;
          case 3:
            strm.msg = 'invalid block type';
            state.mode = BAD;
        }
        //--- DROPBITS(2) ---//
        hold >>>= 2;
        bits -= 2;
        //---//
        break;
      case STORED:
        //--- BYTEBITS() ---// /* go to byte boundary */
        hold >>>= bits & 7;
        bits -= bits & 7;
        //---//
        //=== NEEDBITS(32); */
        while (bits < 32) {
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
        }
        //===//
        if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) {
          strm.msg = 'invalid stored block lengths';
          state.mode = BAD;
          break;
        }
        state.length = hold & 0xffff;
        //Tracev((stderr, "inflate:       stored length %u\n",
        //        state.length));
        //=== INITBITS();
        hold = 0;
        bits = 0;
        //===//
        state.mode = COPY_;
        if (flush === Z_TREES) { break inf_leave; }
        /* falls through */
      case COPY_:
        state.mode = COPY;
        /* falls through */
      case COPY:
        copy = state.length;
        if (copy) {
          if (copy > have) { copy = have; }
          if (copy > left) { copy = left; }
          if (copy === 0) { break inf_leave; }
          //--- zmemcpy(put, next, copy); ---
          utils.arraySet(output, input, next, copy, put);
          //---//
          have -= copy;
          next += copy;
          left -= copy;
          put += copy;
          state.length -= copy;
          break;
        }
        //Tracev((stderr, "inflate:       stored end\n"));
        state.mode = TYPE;
        break;
      case TABLE:
        //=== NEEDBITS(14); */
        while (bits < 14) {
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
        }
        //===//
        state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257;
        //--- DROPBITS(5) ---//
        hold >>>= 5;
        bits -= 5;
        //---//
        state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1;
        //--- DROPBITS(5) ---//
        hold >>>= 5;
        bits -= 5;
        //---//
        state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4;
        //--- DROPBITS(4) ---//
        hold >>>= 4;
        bits -= 4;
        //---//
//#ifndef PKZIP_BUG_WORKAROUND
        if (state.nlen > 286 || state.ndist > 30) {
          strm.msg = 'too many length or distance symbols';
          state.mode = BAD;
          break;
        }
//#endif
        //Tracev((stderr, "inflate:       table sizes ok\n"));
        state.have = 0;
        state.mode = LENLENS;
        /* falls through */
      case LENLENS:
        while (state.have < state.ncode) {
          //=== NEEDBITS(3);
          while (bits < 3) {
            if (have === 0) { break inf_leave; }
            have--;
            hold += input[next++] << bits;
            bits += 8;
          }
          //===//
          state.lens[order[state.have++]] = (hold & 0x07);//BITS(3);
          //--- DROPBITS(3) ---//
          hold >>>= 3;
          bits -= 3;
          //---//
        }
        while (state.have < 19) {
          state.lens[order[state.have++]] = 0;
        }
        // We have separate tables & no pointers. 2 commented lines below not needed.
        //state.next = state.codes;
        //state.lencode = state.next;
        // Switch to use dynamic table
        state.lencode = state.lendyn;
        state.lenbits = 7;

        opts = { bits: state.lenbits };
        ret = inflate_table(CODES, state.lens, 0, 19, state.lencode, 0, state.work, opts);
        state.lenbits = opts.bits;

        if (ret) {
          strm.msg = 'invalid code lengths set';
          state.mode = BAD;
          break;
        }
        //Tracev((stderr, "inflate:       code lengths ok\n"));
        state.have = 0;
        state.mode = CODELENS;
        /* falls through */
      case CODELENS:
        while (state.have < state.nlen + state.ndist) {
          for (;;) {
            here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/
            here_bits = here >>> 24;
            here_op = (here >>> 16) & 0xff;
            here_val = here & 0xffff;

            if ((here_bits) <= bits) { break; }
            //--- PULLBYTE() ---//
            if (have === 0) { break inf_leave; }
            have--;
            hold += input[next++] << bits;
            bits += 8;
            //---//
          }
          if (here_val < 16) {
            //--- DROPBITS(here.bits) ---//
            hold >>>= here_bits;
            bits -= here_bits;
            //---//
            state.lens[state.have++] = here_val;
          }
          else {
            if (here_val === 16) {
              //=== NEEDBITS(here.bits + 2);
              n = here_bits + 2;
              while (bits < n) {
                if (have === 0) { break inf_leave; }
                have--;
                hold += input[next++] << bits;
                bits += 8;
              }
              //===//
              //--- DROPBITS(here.bits) ---//
              hold >>>= here_bits;
              bits -= here_bits;
              //---//
              if (state.have === 0) {
                strm.msg = 'invalid bit length repeat';
                state.mode = BAD;
                break;
              }
              len = state.lens[state.have - 1];
              copy = 3 + (hold & 0x03);//BITS(2);
              //--- DROPBITS(2) ---//
              hold >>>= 2;
              bits -= 2;
              //---//
            }
            else if (here_val === 17) {
              //=== NEEDBITS(here.bits + 3);
              n = here_bits + 3;
              while (bits < n) {
                if (have === 0) { break inf_leave; }
                have--;
                hold += input[next++] << bits;
                bits += 8;
              }
              //===//
              //--- DROPBITS(here.bits) ---//
              hold >>>= here_bits;
              bits -= here_bits;
              //---//
              len = 0;
              copy = 3 + (hold & 0x07);//BITS(3);
              //--- DROPBITS(3) ---//
              hold >>>= 3;
              bits -= 3;
              //---//
            }
            else {
              //=== NEEDBITS(here.bits + 7);
              n = here_bits + 7;
              while (bits < n) {
                if (have === 0) { break inf_leave; }
                have--;
                hold += input[next++] << bits;
                bits += 8;
              }
              //===//
              //--- DROPBITS(here.bits) ---//
              hold >>>= here_bits;
              bits -= here_bits;
              //---//
              len = 0;
              copy = 11 + (hold & 0x7f);//BITS(7);
              //--- DROPBITS(7) ---//
              hold >>>= 7;
              bits -= 7;
              //---//
            }
            if (state.have + copy > state.nlen + state.ndist) {
              strm.msg = 'invalid bit length repeat';
              state.mode = BAD;
              break;
            }
            while (copy--) {
              state.lens[state.have++] = len;
            }
          }
        }

        /* handle error breaks in while */
        if (state.mode === BAD) { break; }

        /* check for end-of-block code (better have one) */
        if (state.lens[256] === 0) {
          strm.msg = 'invalid code -- missing end-of-block';
          state.mode = BAD;
          break;
        }

        /* build code tables -- note: do not change the lenbits or distbits
           values here (9 and 6) without reading the comments in inftrees.h
           concerning the ENOUGH constants, which depend on those values */
        state.lenbits = 9;

        opts = { bits: state.lenbits };
        ret = inflate_table(LENS, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts);
        // We have separate tables & no pointers. 2 commented lines below not needed.
        // state.next_index = opts.table_index;
        state.lenbits = opts.bits;
        // state.lencode = state.next;

        if (ret) {
          strm.msg = 'invalid literal/lengths set';
          state.mode = BAD;
          break;
        }

        state.distbits = 6;
        //state.distcode.copy(state.codes);
        // Switch to use dynamic table
        state.distcode = state.distdyn;
        opts = { bits: state.distbits };
        ret = inflate_table(DISTS, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts);
        // We have separate tables & no pointers. 2 commented lines below not needed.
        // state.next_index = opts.table_index;
        state.distbits = opts.bits;
        // state.distcode = state.next;

        if (ret) {
          strm.msg = 'invalid distances set';
          state.mode = BAD;
          break;
        }
        //Tracev((stderr, 'inflate:       codes ok\n'));
        state.mode = LEN_;
        if (flush === Z_TREES) { break inf_leave; }
        /* falls through */
      case LEN_:
        state.mode = LEN;
        /* falls through */
      case LEN:
        if (have >= 6 && left >= 258) {
          //--- RESTORE() ---
          strm.next_out = put;
          strm.avail_out = left;
          strm.next_in = next;
          strm.avail_in = have;
          state.hold = hold;
          state.bits = bits;
          //---
          inflate_fast(strm, _out);
          //--- LOAD() ---
          put = strm.next_out;
          output = strm.output;
          left = strm.avail_out;
          next = strm.next_in;
          input = strm.input;
          have = strm.avail_in;
          hold = state.hold;
          bits = state.bits;
          //---

          if (state.mode === TYPE) {
            state.back = -1;
          }
          break;
        }
        state.back = 0;
        for (;;) {
          here = state.lencode[hold & ((1 << state.lenbits) - 1)];  /*BITS(state.lenbits)*/
          here_bits = here >>> 24;
          here_op = (here >>> 16) & 0xff;
          here_val = here & 0xffff;

          if (here_bits <= bits) { break; }
          //--- PULLBYTE() ---//
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
          //---//
        }
        if (here_op && (here_op & 0xf0) === 0) {
          last_bits = here_bits;
          last_op = here_op;
          last_val = here_val;
          for (;;) {
            here = state.lencode[last_val +
                    ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)];
            here_bits = here >>> 24;
            here_op = (here >>> 16) & 0xff;
            here_val = here & 0xffff;

            if ((last_bits + here_bits) <= bits) { break; }
            //--- PULLBYTE() ---//
            if (have === 0) { break inf_leave; }
            have--;
            hold += input[next++] << bits;
            bits += 8;
            //---//
          }
          //--- DROPBITS(last.bits) ---//
          hold >>>= last_bits;
          bits -= last_bits;
          //---//
          state.back += last_bits;
        }
        //--- DROPBITS(here.bits) ---//
        hold >>>= here_bits;
        bits -= here_bits;
        //---//
        state.back += here_bits;
        state.length = here_val;
        if (here_op === 0) {
          //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
          //        "inflate:         literal '%c'\n" :
          //        "inflate:         literal 0x%02x\n", here.val));
          state.mode = LIT;
          break;
        }
        if (here_op & 32) {
          //Tracevv((stderr, "inflate:         end of block\n"));
          state.back = -1;
          state.mode = TYPE;
          break;
        }
        if (here_op & 64) {
          strm.msg = 'invalid literal/length code';
          state.mode = BAD;
          break;
        }
        state.extra = here_op & 15;
        state.mode = LENEXT;
        /* falls through */
      case LENEXT:
        if (state.extra) {
          //=== NEEDBITS(state.extra);
          n = state.extra;
          while (bits < n) {
            if (have === 0) { break inf_leave; }
            have--;
            hold += input[next++] << bits;
            bits += 8;
          }
          //===//
          state.length += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/;
          //--- DROPBITS(state.extra) ---//
          hold >>>= state.extra;
          bits -= state.extra;
          //---//
          state.back += state.extra;
        }
        //Tracevv((stderr, "inflate:         length %u\n", state.length));
        state.was = state.length;
        state.mode = DIST;
        /* falls through */
      case DIST:
        for (;;) {
          here = state.distcode[hold & ((1 << state.distbits) - 1)];/*BITS(state.distbits)*/
          here_bits = here >>> 24;
          here_op = (here >>> 16) & 0xff;
          here_val = here & 0xffff;

          if ((here_bits) <= bits) { break; }
          //--- PULLBYTE() ---//
          if (have === 0) { break inf_leave; }
          have--;
          hold += input[next++] << bits;
          bits += 8;
          //---//
        }
        if ((here_op & 0xf0) === 0) {
          last_bits = here_bits;
          last_op = here_op;
          last_val = here_val;
          for (;;) {
            here = state.distcode[last_val +
                    ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)];
            here_bits = here >>> 24;
            here_op = (here >>> 16) & 0xff;
            here_val = here & 0xffff;

            if ((last_bits + here_bits) <= bits) { break; }
            //--- PULLBYTE() ---//
            if (have === 0) { break inf_leave; }
            have--;
            hold += input[next++] << bits;
            bits += 8;
            //---//
          }
          //--- DROPBITS(last.bits) ---//
          hold >>>= last_bits;
          bits -= last_bits;
          //---//
          state.back += last_bits;
        }
        //--- DROPBITS(here.bits) ---//
        hold >>>= here_bits;
        bits -= here_bits;
        //---//
        state.back += here_bits;
        if (here_op & 64) {
          strm.msg = 'invalid distance code';
          state.mode = BAD;
          break;
        }
        state.offset = here_val;
        state.extra = (here_op) & 15;
        state.mode = DISTEXT;
        /* falls through */
      case DISTEXT:
        if (state.extra) {
          //=== NEEDBITS(state.extra);
          n = state.extra;
          while (bits < n) {
            if (have === 0) { break inf_leave; }
            have--;
            hold += input[next++] << bits;
            bits += 8;
          }
          //===//
          state.offset += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/;
          //--- DROPBITS(state.extra) ---//
          hold >>>= state.extra;
          bits -= state.extra;
          //---//
          state.back += state.extra;
        }
//#ifdef INFLATE_STRICT
        if (state.offset > state.dmax) {
          strm.msg = 'invalid distance too far back';
          state.mode = BAD;
          break;
        }
//#endif
        //Tracevv((stderr, "inflate:         distance %u\n", state.offset));
        state.mode = MATCH;
        /* falls through */
      case MATCH:
        if (left === 0) { break inf_leave; }
        copy = _out - left;
        if (state.offset > copy) {         /* copy from window */
          copy = state.offset - copy;
          if (copy > state.whave) {
            if (state.sane) {
              strm.msg = 'invalid distance too far back';
              state.mode = BAD;
              break;
            }
// (!) This block is disabled in zlib defaults,
// don't enable it for binary compatibility
//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
//          Trace((stderr, "inflate.c too far\n"));
//          copy -= state.whave;
//          if (copy > state.length) { copy = state.length; }
//          if (copy > left) { copy = left; }
//          left -= copy;
//          state.length -= copy;
//          do {
//            output[put++] = 0;
//          } while (--copy);
//          if (state.length === 0) { state.mode = LEN; }
//          break;
//#endif
          }
          if (copy > state.wnext) {
            copy -= state.wnext;
            from = state.wsize - copy;
          }
          else {
            from = state.wnext - copy;
          }
          if (copy > state.length) { copy = state.length; }
          from_source = state.window;
        }
        else {                              /* copy from output */
          from_source = output;
          from = put - state.offset;
          copy = state.length;
        }
        if (copy > left) { copy = left; }
        left -= copy;
        state.length -= copy;
        do {
          output[put++] = from_source[from++];
        } while (--copy);
        if (state.length === 0) { state.mode = LEN; }
        break;
      case LIT:
        if (left === 0) { break inf_leave; }
        output[put++] = state.length;
        left--;
        state.mode = LEN;
        break;
      case CHECK:
        if (state.wrap) {
          //=== NEEDBITS(32);
          while (bits < 32) {
            if (have === 0) { break inf_leave; }
            have--;
            // Use '|' instead of '+' to make sure that result is signed
            hold |= input[next++] << bits;
            bits += 8;
          }
          //===//
          _out -= left;
          strm.total_out += _out;
          state.total += _out;
          if (_out) {
            strm.adler = state.check =
                /*UPDATE(state.check, put - _out, _out);*/
                (state.flags ? crc32(state.check, output, _out, put - _out) : adler32(state.check, output, _out, put - _out));

          }
          _out = left;
          // NB: crc32 stored as signed 32-bit int, zswap32 returns signed too
          if ((state.flags ? hold : zswap32(hold)) !== state.check) {
            strm.msg = 'incorrect data check';
            state.mode = BAD;
            break;
          }
          //=== INITBITS();
          hold = 0;
          bits = 0;
          //===//
          //Tracev((stderr, "inflate:   check matches trailer\n"));
        }
        state.mode = LENGTH;
        /* falls through */
      case LENGTH:
        if (state.wrap && state.flags) {
          //=== NEEDBITS(32);
          while (bits < 32) {
            if (have === 0) { break inf_leave; }
            have--;
            hold += input[next++] << bits;
            bits += 8;
          }
          //===//
          if (hold !== (state.total & 0xffffffff)) {
            strm.msg = 'incorrect length check';
            state.mode = BAD;
            break;
          }
          //=== INITBITS();
          hold = 0;
          bits = 0;
          //===//
          //Tracev((stderr, "inflate:   length matches trailer\n"));
        }
        state.mode = DONE;
        /* falls through */
      case DONE:
        ret = Z_STREAM_END;
        break inf_leave;
      case BAD:
        ret = Z_DATA_ERROR;
        break inf_leave;
      case MEM:
        return Z_MEM_ERROR;
      case SYNC:
        /* falls through */
      default:
        return Z_STREAM_ERROR;
    }
  }

  // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave"

  /*
     Return from inflate(), updating the total counts and the check value.
     If there was no progress during the inflate() call, return a buffer
     error.  Call updatewindow() to create and/or update the window state.
     Note: a memory error from inflate() is non-recoverable.
   */

  //--- RESTORE() ---
  strm.next_out = put;
  strm.avail_out = left;
  strm.next_in = next;
  strm.avail_in = have;
  state.hold = hold;
  state.bits = bits;
  //---

  if (state.wsize || (_out !== strm.avail_out && state.mode < BAD &&
                      (state.mode < CHECK || flush !== Z_FINISH))) {
    if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) {
      state.mode = MEM;
      return Z_MEM_ERROR;
    }
  }
  _in -= strm.avail_in;
  _out -= strm.avail_out;
  strm.total_in += _in;
  strm.total_out += _out;
  state.total += _out;
  if (state.wrap && _out) {
    strm.adler = state.check = /*UPDATE(state.check, strm.next_out - _out, _out);*/
      (state.flags ? crc32(state.check, output, _out, strm.next_out - _out) : adler32(state.check, output, _out, strm.next_out - _out));
  }
  strm.data_type = state.bits + (state.last ? 64 : 0) +
                    (state.mode === TYPE ? 128 : 0) +
                    (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0);
  if (((_in === 0 && _out === 0) || flush === Z_FINISH) && ret === Z_OK) {
    ret = Z_BUF_ERROR;
  }
  return ret;
}

function inflateEnd(strm) {

  if (!strm || !strm.state /*|| strm->zfree == (free_func)0*/) {
    return Z_STREAM_ERROR;
  }

  var state = strm.state;
  if (state.window) {
    state.window = null;
  }
  strm.state = null;
  return Z_OK;
}

function inflateGetHeader(strm, head) {
  var state;

  /* check state */
  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
  state = strm.state;
  if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR; }

  /* save header structure */
  state.head = head;
  head.done = false;
  return Z_OK;
}

function inflateSetDictionary(strm, dictionary) {
  var dictLength = dictionary.length;

  var state;
  var dictid;
  var ret;

  /* check state */
  if (!strm /* == Z_NULL */ || !strm.state /* == Z_NULL */) { return Z_STREAM_ERROR; }
  state = strm.state;

  if (state.wrap !== 0 && state.mode !== DICT) {
    return Z_STREAM_ERROR;
  }

  /* check for correct dictionary identifier */
  if (state.mode === DICT) {
    dictid = 1; /* adler32(0, null, 0)*/
    /* dictid = adler32(dictid, dictionary, dictLength); */
    dictid = adler32(dictid, dictionary, dictLength, 0);
    if (dictid !== state.check) {
      return Z_DATA_ERROR;
    }
  }
  /* copy dictionary to window using updatewindow(), which will amend the
   existing dictionary if appropriate */
  ret = updatewindow(strm, dictionary, dictLength, dictLength);
  if (ret) {
    state.mode = MEM;
    return Z_MEM_ERROR;
  }
  state.havedict = 1;
  // Tracev((stderr, "inflate:   dictionary set\n"));
  return Z_OK;
}

exports.inflateReset = inflateReset;
exports.inflateReset2 = inflateReset2;
exports.inflateResetKeep = inflateResetKeep;
exports.inflateInit = inflateInit;
exports.inflateInit2 = inflateInit2;
exports.inflate = inflate;
exports.inflateEnd = inflateEnd;
exports.inflateGetHeader = inflateGetHeader;
exports.inflateSetDictionary = inflateSetDictionary;
exports.inflateInfo = 'pako inflate (from Nodeca project)';

/* Not implemented
exports.inflateCopy = inflateCopy;
exports.inflateGetDictionary = inflateGetDictionary;
exports.inflateMark = inflateMark;
exports.inflatePrime = inflatePrime;
exports.inflateSync = inflateSync;
exports.inflateSyncPoint = inflateSyncPoint;
exports.inflateUndermine = inflateUndermine;
*/

},{"../utils/common":3,"./adler32":5,"./crc32":7,"./inffast":10,"./inftrees":12}],12:[function(require,module,exports){
'use strict';

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

var utils = require('../utils/common');

var MAXBITS = 15;
var ENOUGH_LENS = 852;
var ENOUGH_DISTS = 592;
//var ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS);

var CODES = 0;
var LENS = 1;
var DISTS = 2;

var lbase = [ /* Length codes 257..285 base */
  3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
  35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0
];

var lext = [ /* Length codes 257..285 extra */
  16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18,
  19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78
];

var dbase = [ /* Distance codes 0..29 base */
  1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
  257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
  8193, 12289, 16385, 24577, 0, 0
];

var dext = [ /* Distance codes 0..29 extra */
  16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22,
  23, 23, 24, 24, 25, 25, 26, 26, 27, 27,
  28, 28, 29, 29, 64, 64
];

module.exports = function inflate_table(type, lens, lens_index, codes, table, table_index, work, opts)
{
  var bits = opts.bits;
      //here = opts.here; /* table entry for duplication */

  var len = 0;               /* a code's length in bits */
  var sym = 0;               /* index of code symbols */
  var min = 0, max = 0;          /* minimum and maximum code lengths */
  var root = 0;              /* number of index bits for root table */
  var curr = 0;              /* number of index bits for current table */
  var drop = 0;              /* code bits to drop for sub-table */
  var left = 0;                   /* number of prefix codes available */
  var used = 0;              /* code entries in table used */
  var huff = 0;              /* Huffman code */
  var incr;              /* for incrementing code, index */
  var fill;              /* index for replicating entries */
  var low;               /* low bits for current root entry */
  var mask;              /* mask for low root bits */
  var next;             /* next available space in table */
  var base = null;     /* base value table to use */
  var base_index = 0;
//  var shoextra;    /* extra bits table to use */
  var end;                    /* use base and extra for symbol > end */
  var count = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1];    /* number of codes of each length */
  var offs = new utils.Buf16(MAXBITS + 1); //[MAXBITS+1];     /* offsets in table for each length */
  var extra = null;
  var extra_index = 0;

  var here_bits, here_op, here_val;

  /*
   Process a set of code lengths to create a canonical Huffman code.  The
   code lengths are lens[0..codes-1].  Each length corresponds to the
   symbols 0..codes-1.  The Huffman code is generated by first sorting the
   symbols by length from short to long, and retaining the symbol order
   for codes with equal lengths.  Then the code starts with all zero bits
   for the first code of the shortest length, and the codes are integer
   increments for the same length, and zeros are appended as the length
   increases.  For the deflate format, these bits are stored backwards
   from their more natural integer increment ordering, and so when the
   decoding tables are built in the large loop below, the integer codes
   are incremented backwards.

   This routine assumes, but does not check, that all of the entries in
   lens[] are in the range 0..MAXBITS.  The caller must assure this.
   1..MAXBITS is interpreted as that code length.  zero means that that
   symbol does not occur in this code.

   The codes are sorted by computing a count of codes for each length,
   creating from that a table of starting indices for each length in the
   sorted table, and then entering the symbols in order in the sorted
   table.  The sorted table is work[], with that space being provided by
   the caller.

   The length counts are used for other purposes as well, i.e. finding
   the minimum and maximum length codes, determining if there are any
   codes at all, checking for a valid set of lengths, and looking ahead
   at length counts to determine sub-table sizes when building the
   decoding tables.
   */

  /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */
  for (len = 0; len <= MAXBITS; len++) {
    count[len] = 0;
  }
  for (sym = 0; sym < codes; sym++) {
    count[lens[lens_index + sym]]++;
  }

  /* bound code lengths, force root to be within code lengths */
  root = bits;
  for (max = MAXBITS; max >= 1; max--) {
    if (count[max] !== 0) { break; }
  }
  if (root > max) {
    root = max;
  }
  if (max === 0) {                     /* no symbols to code at all */
    //table.op[opts.table_index] = 64;  //here.op = (var char)64;    /* invalid code marker */
    //table.bits[opts.table_index] = 1;   //here.bits = (var char)1;
    //table.val[opts.table_index++] = 0;   //here.val = (var short)0;
    table[table_index++] = (1 << 24) | (64 << 16) | 0;


    //table.op[opts.table_index] = 64;
    //table.bits[opts.table_index] = 1;
    //table.val[opts.table_index++] = 0;
    table[table_index++] = (1 << 24) | (64 << 16) | 0;

    opts.bits = 1;
    return 0;     /* no symbols, but wait for decoding to report error */
  }
  for (min = 1; min < max; min++) {
    if (count[min] !== 0) { break; }
  }
  if (root < min) {
    root = min;
  }

  /* check for an over-subscribed or incomplete set of lengths */
  left = 1;
  for (len = 1; len <= MAXBITS; len++) {
    left <<= 1;
    left -= count[len];
    if (left < 0) {
      return -1;
    }        /* over-subscribed */
  }
  if (left > 0 && (type === CODES || max !== 1)) {
    return -1;                      /* incomplete set */
  }

  /* generate offsets into symbol table for each length for sorting */
  offs[1] = 0;
  for (len = 1; len < MAXBITS; len++) {
    offs[len + 1] = offs[len] + count[len];
  }

  /* sort symbols by length, by symbol order within each length */
  for (sym = 0; sym < codes; sym++) {
    if (lens[lens_index + sym] !== 0) {
      work[offs[lens[lens_index + sym]]++] = sym;
    }
  }

  /*
   Create and fill in decoding tables.  In this loop, the table being
   filled is at next and has curr index bits.  The code being used is huff
   with length len.  That code is converted to an index by dropping drop
   bits off of the bottom.  For codes where len is less than drop + curr,
   those top drop + curr - len bits are incremented through all values to
   fill the table with replicated entries.

   root is the number of index bits for the root table.  When len exceeds
   root, sub-tables are created pointed to by the root entry with an index
   of the low root bits of huff.  This is saved in low to check for when a
   new sub-table should be started.  drop is zero when the root table is
   being filled, and drop is root when sub-tables are being filled.

   When a new sub-table is needed, it is necessary to look ahead in the
   code lengths to determine what size sub-table is needed.  The length
   counts are used for this, and so count[] is decremented as codes are
   entered in the tables.

   used keeps track of how many table entries have been allocated from the
   provided *table space.  It is checked for LENS and DIST tables against
   the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in
   the initial root table size constants.  See the comments in inftrees.h
   for more information.

   sym increments through all symbols, and the loop terminates when
   all codes of length max, i.e. all codes, have been processed.  This
   routine permits incomplete codes, so another loop after this one fills
   in the rest of the decoding tables with invalid code markers.
   */

  /* set up for code type */
  // poor man optimization - use if-else instead of switch,
  // to avoid deopts in old v8
  if (type === CODES) {
    base = extra = work;    /* dummy value--not used */
    end = 19;

  } else if (type === LENS) {
    base = lbase;
    base_index -= 257;
    extra = lext;
    extra_index -= 257;
    end = 256;

  } else {                    /* DISTS */
    base = dbase;
    extra = dext;
    end = -1;
  }

  /* initialize opts for loop */
  huff = 0;                   /* starting code */
  sym = 0;                    /* starting code symbol */
  len = min;                  /* starting code length */
  next = table_index;              /* current table to fill in */
  curr = root;                /* current table index bits */
  drop = 0;                   /* current bits to drop from code for index */
  low = -1;                   /* trigger new sub-table when len > root */
  used = 1 << root;          /* use root table entries */
  mask = used - 1;            /* mask for comparing low */

  /* check available table space */
  if ((type === LENS && used > ENOUGH_LENS) ||
    (type === DISTS && used > ENOUGH_DISTS)) {
    return 1;
  }

  /* process all codes and make table entries */
  for (;;) {
    /* create table entry */
    here_bits = len - drop;
    if (work[sym] < end) {
      here_op = 0;
      here_val = work[sym];
    }
    else if (work[sym] > end) {
      here_op = extra[extra_index + work[sym]];
      here_val = base[base_index + work[sym]];
    }
    else {
      here_op = 32 + 64;         /* end of block */
      here_val = 0;
    }

    /* replicate for those indices with low len bits equal to huff */
    incr = 1 << (len - drop);
    fill = 1 << curr;
    min = fill;                 /* save offset to next table */
    do {
      fill -= incr;
      table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0;
    } while (fill !== 0);

    /* backwards increment the len-bit code huff */
    incr = 1 << (len - 1);
    while (huff & incr) {
      incr >>= 1;
    }
    if (incr !== 0) {
      huff &= incr - 1;
      huff += incr;
    } else {
      huff = 0;
    }

    /* go to next symbol, update count, len */
    sym++;
    if (--count[len] === 0) {
      if (len === max) { break; }
      len = lens[lens_index + work[sym]];
    }

    /* create new sub-table if needed */
    if (len > root && (huff & mask) !== low) {
      /* if first time, transition to sub-tables */
      if (drop === 0) {
        drop = root;
      }

      /* increment past last table */
      next += min;            /* here min is 1 << curr */

      /* determine length of next table */
      curr = len - drop;
      left = 1 << curr;
      while (curr + drop < max) {
        left -= count[curr + drop];
        if (left <= 0) { break; }
        curr++;
        left <<= 1;
      }

      /* check for enough space */
      used += 1 << curr;
      if ((type === LENS && used > ENOUGH_LENS) ||
        (type === DISTS && used > ENOUGH_DISTS)) {
        return 1;
      }

      /* point entry in root table to sub-table */
      low = huff & mask;
      /*table.op[low] = curr;
      table.bits[low] = root;
      table.val[low] = next - opts.table_index;*/
      table[low] = (root << 24) | (curr << 16) | (next - table_index) |0;
    }
  }

  /* fill in remaining table entry if code is incomplete (guaranteed to have
   at most one remaining entry, since if the code is incomplete, the
   maximum code length that was allowed to get this far is one bit) */
  if (huff !== 0) {
    //table.op[next + huff] = 64;            /* invalid code marker */
    //table.bits[next + huff] = len - drop;
    //table.val[next + huff] = 0;
    table[next + huff] = ((len - drop) << 24) | (64 << 16) |0;
  }

  /* set return parameters */
  //opts.table_index += used;
  opts.bits = root;
  return 0;
};

},{"../utils/common":3}],13:[function(require,module,exports){
'use strict';

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

module.exports = {
  2:      'need dictionary',     /* Z_NEED_DICT       2  */
  1:      'stream end',          /* Z_STREAM_END      1  */
  0:      '',                    /* Z_OK              0  */
  '-1':   'file error',          /* Z_ERRNO         (-1) */
  '-2':   'stream error',        /* Z_STREAM_ERROR  (-2) */
  '-3':   'data error',          /* Z_DATA_ERROR    (-3) */
  '-4':   'insufficient memory', /* Z_MEM_ERROR     (-4) */
  '-5':   'buffer error',        /* Z_BUF_ERROR     (-5) */
  '-6':   'incompatible version' /* Z_VERSION_ERROR (-6) */
};

},{}],14:[function(require,module,exports){
'use strict';

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

var utils = require('../utils/common');

/* Public constants ==========================================================*/
/* ===========================================================================*/


//var Z_FILTERED          = 1;
//var Z_HUFFMAN_ONLY      = 2;
//var Z_RLE               = 3;
var Z_FIXED               = 4;
//var Z_DEFAULT_STRATEGY  = 0;

/* Possible values of the data_type field (though see inflate()) */
var Z_BINARY              = 0;
var Z_TEXT                = 1;
//var Z_ASCII             = 1; // = Z_TEXT
var Z_UNKNOWN             = 2;

/*============================================================================*/


function zero(buf) { var len = buf.length; while (--len >= 0) { buf[len] = 0; } }

// From zutil.h

var STORED_BLOCK = 0;
var STATIC_TREES = 1;
var DYN_TREES    = 2;
/* The three kinds of block type */

var MIN_MATCH    = 3;
var MAX_MATCH    = 258;
/* The minimum and maximum match lengths */

// From deflate.h
/* ===========================================================================
 * Internal compression state.
 */

var LENGTH_CODES  = 29;
/* number of length codes, not counting the special END_BLOCK code */

var LITERALS      = 256;
/* number of literal bytes 0..255 */

var L_CODES       = LITERALS + 1 + LENGTH_CODES;
/* number of Literal or Length codes, including the END_BLOCK code */

var D_CODES       = 30;
/* number of distance codes */

var BL_CODES      = 19;
/* number of codes used to transfer the bit lengths */

var HEAP_SIZE     = 2 * L_CODES + 1;
/* maximum heap size */

var MAX_BITS      = 15;
/* All codes must not exceed MAX_BITS bits */

var Buf_size      = 16;
/* size of bit buffer in bi_buf */


/* ===========================================================================
 * Constants
 */

var MAX_BL_BITS = 7;
/* Bit length codes must not exceed MAX_BL_BITS bits */

var END_BLOCK   = 256;
/* end of block literal code */

var REP_3_6     = 16;
/* repeat previous bit length 3-6 times (2 bits of repeat count) */

var REPZ_3_10   = 17;
/* repeat a zero length 3-10 times  (3 bits of repeat count) */

var REPZ_11_138 = 18;
/* repeat a zero length 11-138 times  (7 bits of repeat count) */

/* eslint-disable comma-spacing,array-bracket-spacing */
var extra_lbits =   /* extra bits for each length code */
  [0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0];

var extra_dbits =   /* extra bits for each distance code */
  [0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13];

var extra_blbits =  /* extra bits for each bit length code */
  [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7];

var bl_order =
  [16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15];
/* eslint-enable comma-spacing,array-bracket-spacing */

/* The lengths of the bit length codes are sent in order of decreasing
 * probability, to avoid transmitting the lengths for unused bit length codes.
 */

/* ===========================================================================
 * Local data. These are initialized only once.
 */

// We pre-fill arrays with 0 to avoid uninitialized gaps

var DIST_CODE_LEN = 512; /* see definition of array dist_code below */

// !!!! Use flat array instead of structure, Freq = i*2, Len = i*2+1
var static_ltree  = new Array((L_CODES + 2) * 2);
zero(static_ltree);
/* The static literal tree. Since the bit lengths are imposed, there is no
 * need for the L_CODES extra codes used during heap construction. However
 * The codes 286 and 287 are needed to build a canonical tree (see _tr_init
 * below).
 */

var static_dtree  = new Array(D_CODES * 2);
zero(static_dtree);
/* The static distance tree. (Actually a trivial tree since all codes use
 * 5 bits.)
 */

var _dist_code    = new Array(DIST_CODE_LEN);
zero(_dist_code);
/* Distance codes. The first 256 values correspond to the distances
 * 3 .. 258, the last 256 values correspond to the top 8 bits of
 * the 15 bit distances.
 */

var _length_code  = new Array(MAX_MATCH - MIN_MATCH + 1);
zero(_length_code);
/* length code for each normalized match length (0 == MIN_MATCH) */

var base_length   = new Array(LENGTH_CODES);
zero(base_length);
/* First normalized length for each code (0 = MIN_MATCH) */

var base_dist     = new Array(D_CODES);
zero(base_dist);
/* First normalized distance for each code (0 = distance of 1) */


function StaticTreeDesc(static_tree, extra_bits, extra_base, elems, max_length) {

  this.static_tree  = static_tree;  /* static tree or NULL */
  this.extra_bits   = extra_bits;   /* extra bits for each code or NULL */
  this.extra_base   = extra_base;   /* base index for extra_bits */
  this.elems        = elems;        /* max number of elements in the tree */
  this.max_length   = max_length;   /* max bit length for the codes */

  // show if `static_tree` has data or dummy - needed for monomorphic objects
  this.has_stree    = static_tree && static_tree.length;
}


var static_l_desc;
var static_d_desc;
var static_bl_desc;


function TreeDesc(dyn_tree, stat_desc) {
  this.dyn_tree = dyn_tree;     /* the dynamic tree */
  this.max_code = 0;            /* largest code with non zero frequency */
  this.stat_desc = stat_desc;   /* the corresponding static tree */
}



function d_code(dist) {
  return dist < 256 ? _dist_code[dist] : _dist_code[256 + (dist >>> 7)];
}


/* ===========================================================================
 * Output a short LSB first on the stream.
 * IN assertion: there is enough room in pendingBuf.
 */
function put_short(s, w) {
//    put_byte(s, (uch)((w) & 0xff));
//    put_byte(s, (uch)((ush)(w) >> 8));
  s.pending_buf[s.pending++] = (w) & 0xff;
  s.pending_buf[s.pending++] = (w >>> 8) & 0xff;
}


/* ===========================================================================
 * Send a value on a given number of bits.
 * IN assertion: length <= 16 and value fits in length bits.
 */
function send_bits(s, value, length) {
  if (s.bi_valid > (Buf_size - length)) {
    s.bi_buf |= (value << s.bi_valid) & 0xffff;
    put_short(s, s.bi_buf);
    s.bi_buf = value >> (Buf_size - s.bi_valid);
    s.bi_valid += length - Buf_size;
  } else {
    s.bi_buf |= (value << s.bi_valid) & 0xffff;
    s.bi_valid += length;
  }
}


function send_code(s, c, tree) {
  send_bits(s, tree[c * 2]/*.Code*/, tree[c * 2 + 1]/*.Len*/);
}


/* ===========================================================================
 * Reverse the first len bits of a code, using straightforward code (a faster
 * method would use a table)
 * IN assertion: 1 <= len <= 15
 */
function bi_reverse(code, len) {
  var res = 0;
  do {
    res |= code & 1;
    code >>>= 1;
    res <<= 1;
  } while (--len > 0);
  return res >>> 1;
}


/* ===========================================================================
 * Flush the bit buffer, keeping at most 7 bits in it.
 */
function bi_flush(s) {
  if (s.bi_valid === 16) {
    put_short(s, s.bi_buf);
    s.bi_buf = 0;
    s.bi_valid = 0;

  } else if (s.bi_valid >= 8) {
    s.pending_buf[s.pending++] = s.bi_buf & 0xff;
    s.bi_buf >>= 8;
    s.bi_valid -= 8;
  }
}


/* ===========================================================================
 * Compute the optimal bit lengths for a tree and update the total bit length
 * for the current block.
 * IN assertion: the fields freq and dad are set, heap[heap_max] and
 *    above are the tree nodes sorted by increasing frequency.
 * OUT assertions: the field len is set to the optimal bit length, the
 *     array bl_count contains the frequencies for each bit length.
 *     The length opt_len is updated; static_len is also updated if stree is
 *     not null.
 */
function gen_bitlen(s, desc)
//    deflate_state *s;
//    tree_desc *desc;    /* the tree descriptor */
{
  var tree            = desc.dyn_tree;
  var max_code        = desc.max_code;
  var stree           = desc.stat_desc.static_tree;
  var has_stree       = desc.stat_desc.has_stree;
  var extra           = desc.stat_desc.extra_bits;
  var base            = desc.stat_desc.extra_base;
  var max_length      = desc.stat_desc.max_length;
  var h;              /* heap index */
  var n, m;           /* iterate over the tree elements */
  var bits;           /* bit length */
  var xbits;          /* extra bits */
  var f;              /* frequency */
  var overflow = 0;   /* number of elements with bit length too large */

  for (bits = 0; bits <= MAX_BITS; bits++) {
    s.bl_count[bits] = 0;
  }

  /* In a first pass, compute the optimal bit lengths (which may
   * overflow in the case of the bit length tree).
   */
  tree[s.heap[s.heap_max] * 2 + 1]/*.Len*/ = 0; /* root of the heap */

  for (h = s.heap_max + 1; h < HEAP_SIZE; h++) {
    n = s.heap[h];
    bits = tree[tree[n * 2 + 1]/*.Dad*/ * 2 + 1]/*.Len*/ + 1;
    if (bits > max_length) {
      bits = max_length;
      overflow++;
    }
    tree[n * 2 + 1]/*.Len*/ = bits;
    /* We overwrite tree[n].Dad which is no longer needed */

    if (n > max_code) { continue; } /* not a leaf node */

    s.bl_count[bits]++;
    xbits = 0;
    if (n >= base) {
      xbits = extra[n - base];
    }
    f = tree[n * 2]/*.Freq*/;
    s.opt_len += f * (bits + xbits);
    if (has_stree) {
      s.static_len += f * (stree[n * 2 + 1]/*.Len*/ + xbits);
    }
  }
  if (overflow === 0) { return; }

  // Trace((stderr,"\nbit length overflow\n"));
  /* This happens for example on obj2 and pic of the Calgary corpus */

  /* Find the first bit length which could increase: */
  do {
    bits = max_length - 1;
    while (s.bl_count[bits] === 0) { bits--; }
    s.bl_count[bits]--;      /* move one leaf down the tree */
    s.bl_count[bits + 1] += 2; /* move one overflow item as its brother */
    s.bl_count[max_length]--;
    /* The brother of the overflow item also moves one step up,
     * but this does not affect bl_count[max_length]
     */
    overflow -= 2;
  } while (overflow > 0);

  /* Now recompute all bit lengths, scanning in increasing frequency.
   * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all
   * lengths instead of fixing only the wrong ones. This idea is taken
   * from 'ar' written by Haruhiko Okumura.)
   */
  for (bits = max_length; bits !== 0; bits--) {
    n = s.bl_count[bits];
    while (n !== 0) {
      m = s.heap[--h];
      if (m > max_code) { continue; }
      if (tree[m * 2 + 1]/*.Len*/ !== bits) {
        // Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits));
        s.opt_len += (bits - tree[m * 2 + 1]/*.Len*/) * tree[m * 2]/*.Freq*/;
        tree[m * 2 + 1]/*.Len*/ = bits;
      }
      n--;
    }
  }
}


/* ===========================================================================
 * Generate the codes for a given tree and bit counts (which need not be
 * optimal).
 * IN assertion: the array bl_count contains the bit length statistics for
 * the given tree and the field len is set for all tree elements.
 * OUT assertion: the field code is set for all tree elements of non
 *     zero code length.
 */
function gen_codes(tree, max_code, bl_count)
//    ct_data *tree;             /* the tree to decorate */
//    int max_code;              /* largest code with non zero frequency */
//    ushf *bl_count;            /* number of codes at each bit length */
{
  var next_code = new Array(MAX_BITS + 1); /* next code value for each bit length */
  var code = 0;              /* running code value */
  var bits;                  /* bit index */
  var n;                     /* code index */

  /* The distribution counts are first used to generate the code values
   * without bit reversal.
   */
  for (bits = 1; bits <= MAX_BITS; bits++) {
    next_code[bits] = code = (code + bl_count[bits - 1]) << 1;
  }
  /* Check that the bit counts in bl_count are consistent. The last code
   * must be all ones.
   */
  //Assert (code + bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1,
  //        "inconsistent bit counts");
  //Tracev((stderr,"\ngen_codes: max_code %d ", max_code));

  for (n = 0;  n <= max_code; n++) {
    var len = tree[n * 2 + 1]/*.Len*/;
    if (len === 0) { continue; }
    /* Now reverse the bits */
    tree[n * 2]/*.Code*/ = bi_reverse(next_code[len]++, len);

    //Tracecv(tree != static_ltree, (stderr,"\nn %3d %c l %2d c %4x (%x) ",
    //     n, (isgraph(n) ? n : ' '), len, tree[n].Code, next_code[len]-1));
  }
}


/* ===========================================================================
 * Initialize the various 'constant' tables.
 */
function tr_static_init() {
  var n;        /* iterates over tree elements */
  var bits;     /* bit counter */
  var length;   /* length value */
  var code;     /* code value */
  var dist;     /* distance index */
  var bl_count = new Array(MAX_BITS + 1);
  /* number of codes at each bit length for an optimal tree */

  // do check in _tr_init()
  //if (static_init_done) return;

  /* For some embedded targets, global variables are not initialized: */
/*#ifdef NO_INIT_GLOBAL_POINTERS
  static_l_desc.static_tree = static_ltree;
  static_l_desc.extra_bits = extra_lbits;
  static_d_desc.static_tree = static_dtree;
  static_d_desc.extra_bits = extra_dbits;
  static_bl_desc.extra_bits = extra_blbits;
#endif*/

  /* Initialize the mapping length (0..255) -> length code (0..28) */
  length = 0;
  for (code = 0; code < LENGTH_CODES - 1; code++) {
    base_length[code] = length;
    for (n = 0; n < (1 << extra_lbits[code]); n++) {
      _length_code[length++] = code;
    }
  }
  //Assert (length == 256, "tr_static_init: length != 256");
  /* Note that the length 255 (match length 258) can be represented
   * in two different ways: code 284 + 5 bits or code 285, so we
   * overwrite length_code[255] to use the best encoding:
   */
  _length_code[length - 1] = code;

  /* Initialize the mapping dist (0..32K) -> dist code (0..29) */
  dist = 0;
  for (code = 0; code < 16; code++) {
    base_dist[code] = dist;
    for (n = 0; n < (1 << extra_dbits[code]); n++) {
      _dist_code[dist++] = code;
    }
  }
  //Assert (dist == 256, "tr_static_init: dist != 256");
  dist >>= 7; /* from now on, all distances are divided by 128 */
  for (; code < D_CODES; code++) {
    base_dist[code] = dist << 7;
    for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) {
      _dist_code[256 + dist++] = code;
    }
  }
  //Assert (dist == 256, "tr_static_init: 256+dist != 512");

  /* Construct the codes of the static literal tree */
  for (bits = 0; bits <= MAX_BITS; bits++) {
    bl_count[bits] = 0;
  }

  n = 0;
  while (n <= 143) {
    static_ltree[n * 2 + 1]/*.Len*/ = 8;
    n++;
    bl_count[8]++;
  }
  while (n <= 255) {
    static_ltree[n * 2 + 1]/*.Len*/ = 9;
    n++;
    bl_count[9]++;
  }
  while (n <= 279) {
    static_ltree[n * 2 + 1]/*.Len*/ = 7;
    n++;
    bl_count[7]++;
  }
  while (n <= 287) {
    static_ltree[n * 2 + 1]/*.Len*/ = 8;
    n++;
    bl_count[8]++;
  }
  /* Codes 286 and 287 do not exist, but we must include them in the
   * tree construction to get a canonical Huffman tree (longest code
   * all ones)
   */
  gen_codes(static_ltree, L_CODES + 1, bl_count);

  /* The static distance tree is trivial: */
  for (n = 0; n < D_CODES; n++) {
    static_dtree[n * 2 + 1]/*.Len*/ = 5;
    static_dtree[n * 2]/*.Code*/ = bi_reverse(n, 5);
  }

  // Now data ready and we can init static trees
  static_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS + 1, L_CODES, MAX_BITS);
  static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0,          D_CODES, MAX_BITS);
  static_bl_desc = new StaticTreeDesc(new Array(0), extra_blbits, 0,         BL_CODES, MAX_BL_BITS);

  //static_init_done = true;
}


/* ===========================================================================
 * Initialize a new block.
 */
function init_block(s) {
  var n; /* iterates over tree elements */

  /* Initialize the trees. */
  for (n = 0; n < L_CODES;  n++) { s.dyn_ltree[n * 2]/*.Freq*/ = 0; }
  for (n = 0; n < D_CODES;  n++) { s.dyn_dtree[n * 2]/*.Freq*/ = 0; }
  for (n = 0; n < BL_CODES; n++) { s.bl_tree[n * 2]/*.Freq*/ = 0; }

  s.dyn_ltree[END_BLOCK * 2]/*.Freq*/ = 1;
  s.opt_len = s.static_len = 0;
  s.last_lit = s.matches = 0;
}


/* ===========================================================================
 * Flush the bit buffer and align the output on a byte boundary
 */
function bi_windup(s)
{
  if (s.bi_valid > 8) {
    put_short(s, s.bi_buf);
  } else if (s.bi_valid > 0) {
    //put_byte(s, (Byte)s->bi_buf);
    s.pending_buf[s.pending++] = s.bi_buf;
  }
  s.bi_buf = 0;
  s.bi_valid = 0;
}

/* ===========================================================================
 * Copy a stored block, storing first the length and its
 * one's complement if requested.
 */
function copy_block(s, buf, len, header)
//DeflateState *s;
//charf    *buf;    /* the input data */
//unsigned len;     /* its length */
//int      header;  /* true if block header must be written */
{
  bi_windup(s);        /* align on byte boundary */

  if (header) {
    put_short(s, len);
    put_short(s, ~len);
  }
//  while (len--) {
//    put_byte(s, *buf++);
//  }
  utils.arraySet(s.pending_buf, s.window, buf, len, s.pending);
  s.pending += len;
}

/* ===========================================================================
 * Compares to subtrees, using the tree depth as tie breaker when
 * the subtrees have equal frequency. This minimizes the worst case length.
 */
function smaller(tree, n, m, depth) {
  var _n2 = n * 2;
  var _m2 = m * 2;
  return (tree[_n2]/*.Freq*/ < tree[_m2]/*.Freq*/ ||
         (tree[_n2]/*.Freq*/ === tree[_m2]/*.Freq*/ && depth[n] <= depth[m]));
}

/* ===========================================================================
 * Restore the heap property by moving down the tree starting at node k,
 * exchanging a node with the smallest of its two sons if necessary, stopping
 * when the heap property is re-established (each father smaller than its
 * two sons).
 */
function pqdownheap(s, tree, k)
//    deflate_state *s;
//    ct_data *tree;  /* the tree to restore */
//    int k;               /* node to move down */
{
  var v = s.heap[k];
  var j = k << 1;  /* left son of k */
  while (j <= s.heap_len) {
    /* Set j to the smallest of the two sons: */
    if (j < s.heap_len &&
      smaller(tree, s.heap[j + 1], s.heap[j], s.depth)) {
      j++;
    }
    /* Exit if v is smaller than both sons */
    if (smaller(tree, v, s.heap[j], s.depth)) { break; }

    /* Exchange v with the smallest son */
    s.heap[k] = s.heap[j];
    k = j;

    /* And continue down the tree, setting j to the left son of k */
    j <<= 1;
  }
  s.heap[k] = v;
}


// inlined manually
// var SMALLEST = 1;

/* ===========================================================================
 * Send the block data compressed using the given Huffman trees
 */
function compress_block(s, ltree, dtree)
//    deflate_state *s;
//    const ct_data *ltree; /* literal tree */
//    const ct_data *dtree; /* distance tree */
{
  var dist;           /* distance of matched string */
  var lc;             /* match length or unmatched char (if dist == 0) */
  var lx = 0;         /* running index in l_buf */
  var code;           /* the code to send */
  var extra;          /* number of extra bits to send */

  if (s.last_lit !== 0) {
    do {
      dist = (s.pending_buf[s.d_buf + lx * 2] << 8) | (s.pending_buf[s.d_buf + lx * 2 + 1]);
      lc = s.pending_buf[s.l_buf + lx];
      lx++;

      if (dist === 0) {
        send_code(s, lc, ltree); /* send a literal byte */
        //Tracecv(isgraph(lc), (stderr," '%c' ", lc));
      } else {
        /* Here, lc is the match length - MIN_MATCH */
        code = _length_code[lc];
        send_code(s, code + LITERALS + 1, ltree); /* send the length code */
        extra = extra_lbits[code];
        if (extra !== 0) {
          lc -= base_length[code];
          send_bits(s, lc, extra);       /* send the extra length bits */
        }
        dist--; /* dist is now the match distance - 1 */
        code = d_code(dist);
        //Assert (code < D_CODES, "bad d_code");

        send_code(s, code, dtree);       /* send the distance code */
        extra = extra_dbits[code];
        if (extra !== 0) {
          dist -= base_dist[code];
          send_bits(s, dist, extra);   /* send the extra distance bits */
        }
      } /* literal or match pair ? */

      /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */
      //Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx,
      //       "pendingBuf overflow");

    } while (lx < s.last_lit);
  }

  send_code(s, END_BLOCK, ltree);
}


/* ===========================================================================
 * Construct one Huffman tree and assigns the code bit strings and lengths.
 * Update the total bit length for the current block.
 * IN assertion: the field freq is set for all tree elements.
 * OUT assertions: the fields len and code are set to the optimal bit length
 *     and corresponding code. The length opt_len is updated; static_len is
 *     also updated if stree is not null. The field max_code is set.
 */
function build_tree(s, desc)
//    deflate_state *s;
//    tree_desc *desc; /* the tree descriptor */
{
  var tree     = desc.dyn_tree;
  var stree    = desc.stat_desc.static_tree;
  var has_stree = desc.stat_desc.has_stree;
  var elems    = desc.stat_desc.elems;
  var n, m;          /* iterate over heap elements */
  var max_code = -1; /* largest code with non zero frequency */
  var node;          /* new node being created */

  /* Construct the initial heap, with least frequent element in
   * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1].
   * heap[0] is not used.
   */
  s.heap_len = 0;
  s.heap_max = HEAP_SIZE;

  for (n = 0; n < elems; n++) {
    if (tree[n * 2]/*.Freq*/ !== 0) {
      s.heap[++s.heap_len] = max_code = n;
      s.depth[n] = 0;

    } else {
      tree[n * 2 + 1]/*.Len*/ = 0;
    }
  }

  /* The pkzip format requires that at least one distance code exists,
   * and that at least one bit should be sent even if there is only one
   * possible code. So to avoid special checks later on we force at least
   * two codes of non zero frequency.
   */
  while (s.heap_len < 2) {
    node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0);
    tree[node * 2]/*.Freq*/ = 1;
    s.depth[node] = 0;
    s.opt_len--;

    if (has_stree) {
      s.static_len -= stree[node * 2 + 1]/*.Len*/;
    }
    /* node is 0 or 1 so it does not have extra bits */
  }
  desc.max_code = max_code;

  /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree,
   * establish sub-heaps of increasing lengths:
   */
  for (n = (s.heap_len >> 1/*int /2*/); n >= 1; n--) { pqdownheap(s, tree, n); }

  /* Construct the Huffman tree by repeatedly combining the least two
   * frequent nodes.
   */
  node = elems;              /* next internal node of the tree */
  do {
    //pqremove(s, tree, n);  /* n = node of least frequency */
    /*** pqremove ***/
    n = s.heap[1/*SMALLEST*/];
    s.heap[1/*SMALLEST*/] = s.heap[s.heap_len--];
    pqdownheap(s, tree, 1/*SMALLEST*/);
    /***/

    m = s.heap[1/*SMALLEST*/]; /* m = node of next least frequency */

    s.heap[--s.heap_max] = n; /* keep the nodes sorted by frequency */
    s.heap[--s.heap_max] = m;

    /* Create a new node father of n and m */
    tree[node * 2]/*.Freq*/ = tree[n * 2]/*.Freq*/ + tree[m * 2]/*.Freq*/;
    s.depth[node] = (s.depth[n] >= s.depth[m] ? s.depth[n] : s.depth[m]) + 1;
    tree[n * 2 + 1]/*.Dad*/ = tree[m * 2 + 1]/*.Dad*/ = node;

    /* and insert the new node in the heap */
    s.heap[1/*SMALLEST*/] = node++;
    pqdownheap(s, tree, 1/*SMALLEST*/);

  } while (s.heap_len >= 2);

  s.heap[--s.heap_max] = s.heap[1/*SMALLEST*/];

  /* At this point, the fields freq and dad are set. We can now
   * generate the bit lengths.
   */
  gen_bitlen(s, desc);

  /* The field len is now set, we can generate the bit codes */
  gen_codes(tree, max_code, s.bl_count);
}


/* ===========================================================================
 * Scan a literal or distance tree to determine the frequencies of the codes
 * in the bit length tree.
 */
function scan_tree(s, tree, max_code)
//    deflate_state *s;
//    ct_data *tree;   /* the tree to be scanned */
//    int max_code;    /* and its largest code of non zero frequency */
{
  var n;                     /* iterates over all tree elements */
  var prevlen = -1;          /* last emitted length */
  var curlen;                /* length of current code */

  var nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */

  var count = 0;             /* repeat count of the current code */
  var max_count = 7;         /* max repeat count */
  var min_count = 4;         /* min repeat count */

  if (nextlen === 0) {
    max_count = 138;
    min_count = 3;
  }
  tree[(max_code + 1) * 2 + 1]/*.Len*/ = 0xffff; /* guard */

  for (n = 0; n <= max_code; n++) {
    curlen = nextlen;
    nextlen = tree[(n + 1) * 2 + 1]/*.Len*/;

    if (++count < max_count && curlen === nextlen) {
      continue;

    } else if (count < min_count) {
      s.bl_tree[curlen * 2]/*.Freq*/ += count;

    } else if (curlen !== 0) {

      if (curlen !== prevlen) { s.bl_tree[curlen * 2]/*.Freq*/++; }
      s.bl_tree[REP_3_6 * 2]/*.Freq*/++;

    } else if (count <= 10) {
      s.bl_tree[REPZ_3_10 * 2]/*.Freq*/++;

    } else {
      s.bl_tree[REPZ_11_138 * 2]/*.Freq*/++;
    }

    count = 0;
    prevlen = curlen;

    if (nextlen === 0) {
      max_count = 138;
      min_count = 3;

    } else if (curlen === nextlen) {
      max_count = 6;
      min_count = 3;

    } else {
      max_count = 7;
      min_count = 4;
    }
  }
}


/* ===========================================================================
 * Send a literal or distance tree in compressed form, using the codes in
 * bl_tree.
 */
function send_tree(s, tree, max_code)
//    deflate_state *s;
//    ct_data *tree; /* the tree to be scanned */
//    int max_code;       /* and its largest code of non zero frequency */
{
  var n;                     /* iterates over all tree elements */
  var prevlen = -1;          /* last emitted length */
  var curlen;                /* length of current code */

  var nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */

  var count = 0;             /* repeat count of the current code */
  var max_count = 7;         /* max repeat count */
  var min_count = 4;         /* min repeat count */

  /* tree[max_code+1].Len = -1; */  /* guard already set */
  if (nextlen === 0) {
    max_count = 138;
    min_count = 3;
  }

  for (n = 0; n <= max_code; n++) {
    curlen = nextlen;
    nextlen = tree[(n + 1) * 2 + 1]/*.Len*/;

    if (++count < max_count && curlen === nextlen) {
      continue;

    } else if (count < min_count) {
      do { send_code(s, curlen, s.bl_tree); } while (--count !== 0);

    } else if (curlen !== 0) {
      if (curlen !== prevlen) {
        send_code(s, curlen, s.bl_tree);
        count--;
      }
      //Assert(count >= 3 && count <= 6, " 3_6?");
      send_code(s, REP_3_6, s.bl_tree);
      send_bits(s, count - 3, 2);

    } else if (count <= 10) {
      send_code(s, REPZ_3_10, s.bl_tree);
      send_bits(s, count - 3, 3);

    } else {
      send_code(s, REPZ_11_138, s.bl_tree);
      send_bits(s, count - 11, 7);
    }

    count = 0;
    prevlen = curlen;
    if (nextlen === 0) {
      max_count = 138;
      min_count = 3;

    } else if (curlen === nextlen) {
      max_count = 6;
      min_count = 3;

    } else {
      max_count = 7;
      min_count = 4;
    }
  }
}


/* ===========================================================================
 * Construct the Huffman tree for the bit lengths and return the index in
 * bl_order of the last bit length code to send.
 */
function build_bl_tree(s) {
  var max_blindex;  /* index of last bit length code of non zero freq */

  /* Determine the bit length frequencies for literal and distance trees */
  scan_tree(s, s.dyn_ltree, s.l_desc.max_code);
  scan_tree(s, s.dyn_dtree, s.d_desc.max_code);

  /* Build the bit length tree: */
  build_tree(s, s.bl_desc);
  /* opt_len now includes the length of the tree representations, except
   * the lengths of the bit lengths codes and the 5+5+4 bits for the counts.
   */

  /* Determine the number of bit length codes to send. The pkzip format
   * requires that at least 4 bit length codes be sent. (appnote.txt says
   * 3 but the actual value used is 4.)
   */
  for (max_blindex = BL_CODES - 1; max_blindex >= 3; max_blindex--) {
    if (s.bl_tree[bl_order[max_blindex] * 2 + 1]/*.Len*/ !== 0) {
      break;
    }
  }
  /* Update opt_len to include the bit length tree and counts */
  s.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4;
  //Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld",
  //        s->opt_len, s->static_len));

  return max_blindex;
}


/* ===========================================================================
 * Send the header for a block using dynamic Huffman trees: the counts, the
 * lengths of the bit length codes, the literal tree and the distance tree.
 * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4.
 */
function send_all_trees(s, lcodes, dcodes, blcodes)
//    deflate_state *s;
//    int lcodes, dcodes, blcodes; /* number of codes for each tree */
{
  var rank;                    /* index in bl_order */

  //Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes");
  //Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES,
  //        "too many codes");
  //Tracev((stderr, "\nbl counts: "));
  send_bits(s, lcodes - 257, 5); /* not +255 as stated in appnote.txt */
  send_bits(s, dcodes - 1,   5);
  send_bits(s, blcodes - 4,  4); /* not -3 as stated in appnote.txt */
  for (rank = 0; rank < blcodes; rank++) {
    //Tracev((stderr, "\nbl code %2d ", bl_order[rank]));
    send_bits(s, s.bl_tree[bl_order[rank] * 2 + 1]/*.Len*/, 3);
  }
  //Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent));

  send_tree(s, s.dyn_ltree, lcodes - 1); /* literal tree */
  //Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent));

  send_tree(s, s.dyn_dtree, dcodes - 1); /* distance tree */
  //Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent));
}


/* ===========================================================================
 * Check if the data type is TEXT or BINARY, using the following algorithm:
 * - TEXT if the two conditions below are satisfied:
 *    a) There are no non-portable control characters belonging to the
 *       "black list" (0..6, 14..25, 28..31).
 *    b) There is at least one printable character belonging to the
 *       "white list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255).
 * - BINARY otherwise.
 * - The following partially-portable control characters form a
 *   "gray list" that is ignored in this detection algorithm:
 *   (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}).
 * IN assertion: the fields Freq of dyn_ltree are set.
 */
function detect_data_type(s) {
  /* black_mask is the bit mask of black-listed bytes
   * set bits 0..6, 14..25, and 28..31
   * 0xf3ffc07f = binary 11110011111111111100000001111111
   */
  var black_mask = 0xf3ffc07f;
  var n;

  /* Check for non-textual ("black-listed") bytes. */
  for (n = 0; n <= 31; n++, black_mask >>>= 1) {
    if ((black_mask & 1) && (s.dyn_ltree[n * 2]/*.Freq*/ !== 0)) {
      return Z_BINARY;
    }
  }

  /* Check for textual ("white-listed") bytes. */
  if (s.dyn_ltree[9 * 2]/*.Freq*/ !== 0 || s.dyn_ltree[10 * 2]/*.Freq*/ !== 0 ||
      s.dyn_ltree[13 * 2]/*.Freq*/ !== 0) {
    return Z_TEXT;
  }
  for (n = 32; n < LITERALS; n++) {
    if (s.dyn_ltree[n * 2]/*.Freq*/ !== 0) {
      return Z_TEXT;
    }
  }

  /* There are no "black-listed" or "white-listed" bytes:
   * this stream either is empty or has tolerated ("gray-listed") bytes only.
   */
  return Z_BINARY;
}


var static_init_done = false;

/* ===========================================================================
 * Initialize the tree data structures for a new zlib stream.
 */
function _tr_init(s)
{

  if (!static_init_done) {
    tr_static_init();
    static_init_done = true;
  }

  s.l_desc  = new TreeDesc(s.dyn_ltree, static_l_desc);
  s.d_desc  = new TreeDesc(s.dyn_dtree, static_d_desc);
  s.bl_desc = new TreeDesc(s.bl_tree, static_bl_desc);

  s.bi_buf = 0;
  s.bi_valid = 0;

  /* Initialize the first block of the first file: */
  init_block(s);
}


/* ===========================================================================
 * Send a stored block
 */
function _tr_stored_block(s, buf, stored_len, last)
//DeflateState *s;
//charf *buf;       /* input block */
//ulg stored_len;   /* length of input block */
//int last;         /* one if this is the last block for a file */
{
  send_bits(s, (STORED_BLOCK << 1) + (last ? 1 : 0), 3);    /* send block type */
  copy_block(s, buf, stored_len, true); /* with header */
}


/* ===========================================================================
 * Send one empty static block to give enough lookahead for inflate.
 * This takes 10 bits, of which 7 may remain in the bit buffer.
 */
function _tr_align(s) {
  send_bits(s, STATIC_TREES << 1, 3);
  send_code(s, END_BLOCK, static_ltree);
  bi_flush(s);
}


/* ===========================================================================
 * Determine the best encoding for the current block: dynamic trees, static
 * trees or store, and output the encoded block to the zip file.
 */
function _tr_flush_block(s, buf, stored_len, last)
//DeflateState *s;
//charf *buf;       /* input block, or NULL if too old */
//ulg stored_len;   /* length of input block */
//int last;         /* one if this is the last block for a file */
{
  var opt_lenb, static_lenb;  /* opt_len and static_len in bytes */
  var max_blindex = 0;        /* index of last bit length code of non zero freq */

  /* Build the Huffman trees unless a stored block is forced */
  if (s.level > 0) {

    /* Check if the file is binary or text */
    if (s.strm.data_type === Z_UNKNOWN) {
      s.strm.data_type = detect_data_type(s);
    }

    /* Construct the literal and distance trees */
    build_tree(s, s.l_desc);
    // Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len,
    //        s->static_len));

    build_tree(s, s.d_desc);
    // Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len,
    //        s->static_len));
    /* At this point, opt_len and static_len are the total bit lengths of
     * the compressed block data, excluding the tree representations.
     */

    /* Build the bit length tree for the above two trees, and get the index
     * in bl_order of the last bit length code to send.
     */
    max_blindex = build_bl_tree(s);

    /* Determine the best encoding. Compute the block lengths in bytes. */
    opt_lenb = (s.opt_len + 3 + 7) >>> 3;
    static_lenb = (s.static_len + 3 + 7) >>> 3;

    // Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ",
    //        opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len,
    //        s->last_lit));

    if (static_lenb <= opt_lenb) { opt_lenb = static_lenb; }

  } else {
    // Assert(buf != (char*)0, "lost buf");
    opt_lenb = static_lenb = stored_len + 5; /* force a stored block */
  }

  if ((stored_len + 4 <= opt_lenb) && (buf !== -1)) {
    /* 4: two words for the lengths */

    /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE.
     * Otherwise we can't have processed more than WSIZE input bytes since
     * the last block flush, because compression would have been
     * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to
     * transform a block into a stored block.
     */
    _tr_stored_block(s, buf, stored_len, last);

  } else if (s.strategy === Z_FIXED || static_lenb === opt_lenb) {

    send_bits(s, (STATIC_TREES << 1) + (last ? 1 : 0), 3);
    compress_block(s, static_ltree, static_dtree);

  } else {
    send_bits(s, (DYN_TREES << 1) + (last ? 1 : 0), 3);
    send_all_trees(s, s.l_desc.max_code + 1, s.d_desc.max_code + 1, max_blindex + 1);
    compress_block(s, s.dyn_ltree, s.dyn_dtree);
  }
  // Assert (s->compressed_len == s->bits_sent, "bad compressed size");
  /* The above check is made mod 2^32, for files larger than 512 MB
   * and uLong implemented on 32 bits.
   */
  init_block(s);

  if (last) {
    bi_windup(s);
  }
  // Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3,
  //       s->compressed_len-7*last));
}

/* ===========================================================================
 * Save the match info and tally the frequency counts. Return true if
 * the current block must be flushed.
 */
function _tr_tally(s, dist, lc)
//    deflate_state *s;
//    unsigned dist;  /* distance of matched string */
//    unsigned lc;    /* match length-MIN_MATCH or unmatched char (if dist==0) */
{
  //var out_length, in_length, dcode;

  s.pending_buf[s.d_buf + s.last_lit * 2]     = (dist >>> 8) & 0xff;
  s.pending_buf[s.d_buf + s.last_lit * 2 + 1] = dist & 0xff;

  s.pending_buf[s.l_buf + s.last_lit] = lc & 0xff;
  s.last_lit++;

  if (dist === 0) {
    /* lc is the unmatched char */
    s.dyn_ltree[lc * 2]/*.Freq*/++;
  } else {
    s.matches++;
    /* Here, lc is the match length - MIN_MATCH */
    dist--;             /* dist = match distance - 1 */
    //Assert((ush)dist < (ush)MAX_DIST(s) &&
    //       (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) &&
    //       (ush)d_code(dist) < (ush)D_CODES,  "_tr_tally: bad match");

    s.dyn_ltree[(_length_code[lc] + LITERALS + 1) * 2]/*.Freq*/++;
    s.dyn_dtree[d_code(dist) * 2]/*.Freq*/++;
  }

// (!) This block is disabled in zlib defaults,
// don't enable it for binary compatibility

//#ifdef TRUNCATE_BLOCK
//  /* Try to guess if it is profitable to stop the current block here */
//  if ((s.last_lit & 0x1fff) === 0 && s.level > 2) {
//    /* Compute an upper bound for the compressed length */
//    out_length = s.last_lit*8;
//    in_length = s.strstart - s.block_start;
//
//    for (dcode = 0; dcode < D_CODES; dcode++) {
//      out_length += s.dyn_dtree[dcode*2]/*.Freq*/ * (5 + extra_dbits[dcode]);
//    }
//    out_length >>>= 3;
//    //Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ",
//    //       s->last_lit, in_length, out_length,
//    //       100L - out_length*100L/in_length));
//    if (s.matches < (s.last_lit>>1)/*int /2*/ && out_length < (in_length>>1)/*int /2*/) {
//      return true;
//    }
//  }
//#endif

  return (s.last_lit === s.lit_bufsize - 1);
  /* We avoid equality with lit_bufsize because of wraparound at 64K
   * on 16 bit machines and because stored blocks are restricted to
   * 64K-1 bytes.
   */
}

exports._tr_init  = _tr_init;
exports._tr_stored_block = _tr_stored_block;
exports._tr_flush_block  = _tr_flush_block;
exports._tr_tally = _tr_tally;
exports._tr_align = _tr_align;

},{"../utils/common":3}],15:[function(require,module,exports){
'use strict';

// (C) 1995-2013 Jean-loup Gailly and Mark Adler
// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
//
// This software is provided 'as-is', without any express or implied
// warranty. In no event will the authors be held liable for any damages
// arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it
// freely, subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented; you must not
//   claim that you wrote the original software. If you use this software
//   in a product, an acknowledgment in the product documentation would be
//   appreciated but is not required.
// 2. Altered source versions must be plainly marked as such, and must not be
//   misrepresented as being the original software.
// 3. This notice may not be removed or altered from any source distribution.

function ZStream() {
  /* next input byte */
  this.input = null; // JS specific, because we have no pointers
  this.next_in = 0;
  /* number of bytes available at input */
  this.avail_in = 0;
  /* total number of input bytes read so far */
  this.total_in = 0;
  /* next output byte should be put there */
  this.output = null; // JS specific, because we have no pointers
  this.next_out = 0;
  /* remaining free space at output */
  this.avail_out = 0;
  /* total number of bytes output so far */
  this.total_out = 0;
  /* last error message, NULL if no error */
  this.msg = ''/*Z_NULL*/;
  /* not visible by applications */
  this.state = null;
  /* best guess about the data type: binary or text */
  this.data_type = 2/*Z_UNKNOWN*/;
  /* adler32 value of the uncompressed data */
  this.adler = 0;
}

module.exports = ZStream;

},{}],"/":[function(require,module,exports){
// Top level file is just a mixin of submodules & constants
'use strict';

var assign    = require('./lib/utils/common').assign;

var deflate   = require('./lib/deflate');
var inflate   = require('./lib/inflate');
var constants = require('./lib/zlib/constants');

var pako = {};

assign(pako, deflate, inflate, constants);

module.exports = pako;

},{"./lib/deflate":1,"./lib/inflate":2,"./lib/utils/common":3,"./lib/zlib/constants":6}]},{},[])("/")
});;
EdelweissAnalytics.LaneSettings = new (function () {
    this.tempCategoryExist = false;
    this.previousCategoryTypeForComparison = 0;

    this.selectNewCatalog = function (htmlStr, catalogSearchSource, laneKey) {
        $("#selectedCatalog").hide();
        $("#catalogSearch").show();
        $("#catalogSearch").html(htmlStr);
        this.loadCategoryAutocomplete(catalogSearchSource, laneKey);
    };

    this.setMinimumLastSoldDate = function (selValue, dashType, prefName) {
        var isoDateString = selValue.toISOString();
        $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", {
            type: "analytics",
            name: prefName,
            value: isoDateString
        }, function () {
            EdelweissAnalytics.filterOptions[dashType].minimumLastSoldDate = isoDateString;
            EdelweissAnalytics.laneSettingChange[dashType] = true;
        });
    };

    this.loadCategoryAutocomplete = function (catalogSearchSource, laneKey) {
        $("#catalogSearch").load("/GetTreelineControl.aspx?controlName=/uc/catalog/CatalogQuickSearch.ascx"
            + "&source=" + catalogSearchSource + "&laneKey=" + laneKey);        
    };

    this.saveMarketPrefs = function (prefValue, prefName, dashType) {
        $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", {
            type: "analytics",
            name: prefName,
            value: prefValue
        }, function () {
            EdelweissAnalytics.addToMarketFilters(dashType, prefValue);
            if ($("#analytics_content").is(":visible") && $("#analytics_content").length > 0) {
                EdelweissAnalytics.reLoadAnalyticsDetail[dashType] = true;
            }
            EdelweissAnalytics.laneSettingChange[dashType] = true;
        });
    };

    this.saveTimeFramePrefs = function (prefValue, prefName, dashType, laneKey) {
        $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", { 
            type: "analytics", 
            name: prefName, 
            value: prefValue 
        }, function () {
            EdelweissAnalytics.filterOptions[dashType].monthsBack = prefValue;
            if (laneKey === EdelweissAnalytics.LaneKeys.TrendsAnalysis) {
                EdelweissAnalytics.wasTrendsAnalysisPointClicked = true;
            }
            EdelweissAnalytics.laneSettingChange[dashType] = true;
        });

    };

     this.saveNumTopPrefs = function(prefValue, prefName, dashType) {
        $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", { 
            type: "analytics", 
            name: prefName, 
            value: prefValue 
        }, function () {               
            EdelweissAnalytics.filterOptions[dashType].numberRequested = prefValue;
            EdelweissAnalytics.laneSettingChange[dashType] = true;
        });
     }

     this.saveMinimumCopiesOnHandPrefs = function (prefValue, prefName, dashType) {
         $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", {
             type: 'analytics',
             name: prefName,
             value: prefValue
         }, function () {
             EdelweissAnalytics.filterOptions[dashType].minimumCopiesOnHand = prefValue;
             EdelweissAnalytics.laneSettingChange[dashType] = true;
         });
    }

     this.saveCategoryPrefs = function (prefValue, prefName, dashType) {
         var _this = this;
         $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", {
             type: 'analytics',
             name: prefName,
             value: prefValue
         }, function () {
             _this.previousCategoryTypeForComparison = EdelweissAnalytics.categoryTypeForComparison;
             EdelweissAnalytics.categoryTypeForComparison = prefValue;             
             _this.tempCategoryExist = EdelweissAnalytics.temporaryCategoryFilterPath.length > 0;
             if (!_this.tempCategoryExist) {
                 EdelweissAnalytics.laneSettingChange[dashType] = true;
             }
         });
     }

     this.savePeerOrgPrefs = function (prefValue, prefName, dashType) {
         $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", { 
             type: 'analytics', 
             name: prefName, 
             value: prefValue
         }, function () {      
             EdelweissAnalytics.filterOptions[dashType].peerOrgId = prefValue;
             EdelweissAnalytics.laneSettingChange[dashType] = true;
         });
     }

     this.savePeerBranchPrefs = function (prefValue, prefName, dashType) {
         $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", { 
             type: 'analytics', 
             name: prefName, 
             value: prefValue
         }, function () {    
             EdelweissAnalytics.filterOptions[dashType].peerBranchId = prefValue;
             EdelweissAnalytics.laneSettingChange[dashType] = true;
         });
    }

    this.initializeDatePicker = function (element, cultureName, defaultDate) {
        if (_.isNull(defaultDate)) {
            defaultDate = new Date();
        }

        $(element).datepicker($.extend({ onSelect: function () { $(this).change(); } },
            $.datepicker.regional[cultureName]
        )).datepicker("setDate", defaultDate);

        $("#ui-datepicker-div").click(function (event) {
            event.stopPropagation();
        });
    }

    this.initializeDateRangePicker = function(fromElement, toElement,
        pubDateLowerBoundString, pubDateUpperBoundString, cultureName,
        prefPubDateLowerBound, prefPubDateUpperBound, dashType) {

        var options = $.datepicker.regional[cultureName];
        options["minDate"] = 0; // minimum date is today

        if (_.isEmpty(pubDateLowerBoundString)) {
            var fromDefaultDate = new Date();
        } else {
            var fromDefaultDate = new Date(pubDateLowerBoundString);
        }

        var from = $(fromElement).datepicker(options).datepicker("setDate", fromDefaultDate);
        from.on("change", function () {
            if ($("#includeUpperBoundCheckBox").prop("checked")) {
                var fromDate = $(fromElement).datepicker("getDate");
                var toDate = $(toElement).datepicker("getDate");
                if (moment(fromDate).isAfter(toDate, 'day')) {
                    $(toElement).datepicker("setDate", fromDate);
                }
            }
            EdelweissAnalytics.laneSettingChange[dashType] = true;
        });

        var to = $(toElement).datepicker(options)
        to.on("change", function () {
            var fromDate = $(fromElement).datepicker("getDate");
            var toDate = $(toElement).datepicker("getDate");
            if (moment(toDate).isBefore(fromDate, 'day')) {
                $(fromElement).datepicker("setDate", toDate);
            }
            EdelweissAnalytics.laneSettingChange[dashType] = true;
        });

        if (!_.isEmpty(pubDateUpperBoundString) && pubDateUpperBoundString !== "*") {
            var toDefaultDate = new Date(pubDateUpperBoundString);
            to.datepicker("setDate", toDefaultDate);
            $("#pubDateUpperBound_wrapper").show();
            $("#includeUpperBoundCheckBox").prop("checked", true);
        }

        $("#includeUpperBoundCheckBox").click(function() {
            if ($("#includeUpperBoundCheckBox").prop("checked")) {
                var fromDate = $(fromElement).datepicker("getDate");
                $(toElement).datepicker("setDate", fromDate);
                $("#pubDateUpperBound_wrapper").show();
            } else {
                $("#pubDateUpperBound_wrapper").hide();
            }
            EdelweissAnalytics.laneSettingChange[dashType] = true;
        });

        $("#ui-datepicker-div").click(function (event) {
             event.stopPropagation();
        });
    }

     this.initializeJqueryDateRangePickerAsHomepageWidgetOption = function (fromElement, toElement,
             lowerBoundPrefValue, upperBoundPrefValue, cultureName, minDate, widgetId, resultType, laneId,
             lanePrefTypeForLowerBound, lanePrefTypeForUpperBound) {
         var options = $.datepicker.regional[cultureName];
         options["minDate"] = 0; // minimum date is today

         if (_.isEmpty(lowerBoundPrefValue)) {
             var fromDefaultDate = minDate;
         } else {
             var fromDefaultDate = new Date(lowerBoundPrefValue);
         }

         var isShowingUpperBound = false;
         if (_.isEmpty(upperBoundPrefValue) || upperBoundPrefValue === "*") {
             toDefaultDate = minDate;
         } else {
             $("#pubDateUpperBound_wrapper").show();
             $("#includeUpperBoundCheckBox").prop("checked", true);
             var toDefaultDate = new Date(upperBoundPrefValue);
             var isShowingUpperBound = true;
         }

         var from = $(fromElement).datepicker(options)
             .on("change", function () {
                 var fromDate = $(fromElement).datepicker("getDate");
                 $(toElement).datepicker("option", "minDate", fromDate);
                 var fromDateStringForSolr = fromDate.toISOString();
                 var settings = {
                     widgetId: widgetId,
                     resultType: resultType,
                     laneId: laneId,
                     prefType: lanePrefTypeForLowerBound,
                     prefValue: fromDateStringForSolr
                 };
                 window.ePlus.modules.dashboard.saveWidgetPreference(settings);
             })
             .datepicker("setDate", fromDefaultDate);
         if (isShowingUpperBound) {
             $(fromElement).datepicker("option", "maxDate", toDefaultDate);
         }

         var to = $(toElement).datepicker(options)
            .on("change", function () {
                var toDate = $(toElement).datepicker("getDate");
                $(fromElement).datepicker("option", "maxDate", toDate);
                var toDateStringForSolr = toDate.toISOString();
                var settings = {
                    widgetId: widgetId,
                    resultType: resultType,
                    laneId: laneId,
                    prefType: lanePrefTypeForUpperBound,
                    prefValue: toDateStringForSolr
                };
                window.ePlus.modules.dashboard.saveWidgetPreference(settings);
             })
             .datepicker("setDate", toDefaultDate)
             .datepicker("option", "minDate", fromDefaultDate);

         $("#ui-datepicker-div").click(function (event) {
             event.stopPropagation();
         });
     };

        
    this.toggleStockedOnly = function (elem, prefName, dashType) {
        // checked means the ball is on the right side: all titles are included for the analysis
        // otherwise, only stocked titles are included
        var includeNeverStockedSegment;
        if (elem.attr("checked") === "checked") {
            includeNeverStockedSegment = true;
        } else {
            includeNeverStockedSegment = false;
        }
        $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", {
            type: "analytics",
            name: prefName,
            value: includeNeverStockedSegment
        }, function () {
            EdelweissAnalytics.filterOptions[dashType].includeNeverStockedSegment = includeNeverStockedSegment;
            EdelweissAnalytics.laneSettingChange[dashType] = true;
        });
    };

    this.toggleSegmentationModes = function (elem, prefName, dashType) {
        // checked means the ball is on the right side: the segmentation mode is activity mode
        // otherwise, it is turn mode
        var segmentationMode = 0;
        if (elem.attr("checked") === "checked") {
            segmentationMode = EdelweissAnalytics.segmentationModes.Activity;
            $("#stockedOnlyOptionWrap_" + dashType).hide();
        } else {
            segmentationMode = EdelweissAnalytics.segmentationModes.Turn
            $("#stockedOnlyOptionWrap_" + dashType).show();
        }
        $.getJSON("/getJSONData.aspx?builder=SaveUserPreference", { 
            type: "analytics", 
            name: prefName, 
            value: segmentationMode 
        }, function () {
            EdelweissAnalytics.laneSettingChange[dashType] = true;
            EdelweissAnalytics.filterOptions[dashType].segmentationMode = segmentationMode;
            var  stockAnalysisClass = EdelweissAnalytics.filterOptions[dashType].stockAnalysisClass;
            var sortDirection = "desc";
            var sortColumnName = "AI"
            var labelLow = "";
            var labelMedium = "";
            var labelHigh = "";
            var inputLower = 0;
            var inputHigher = 0;
            var explanationText = "";
            
            if(segmentationMode === EdelweissAnalytics.segmentationModes.Activity) {
                if (stockAnalysisClass === 2) {
                    sortDirection = "asc";
                }
                labelLow = getRes("fresh") + " < ";
                labelMedium = " < " + getRes("stable") + " < ";
                labelHigh = " < " + getRes("stale");
                inputLower = EdelweissAnalytics.filterOptions[dashType].inventoryIndexRangeLower;
                inputHigher = EdelweissAnalytics.filterOptions[dashType].inventoryIndexRangeUpper;
                explanationText = getRes("shelf_days_is_combined_number_days_all_copies_on_shelf_no_activity");
            } else if(segmentationMode === EdelweissAnalytics.segmentationModes.Turn) {
                sortColumnName = "Turn";
                labelLow = getRes("low") + " < ";
                labelMedium = " < " + getRes("medium") + " < ";
                labelHigh = " < " + getRes("high");
                inputLower = EdelweissAnalytics.filterOptions[dashType].turnRangeLower;
                inputHigher = EdelweissAnalytics.filterOptions[dashType].turnRangeUpper;
                if (EdelweissAnalytics.doUseRetailView) {
                    explanationText = getRes("turn_calculation_note_for_retailer");
                } else {
                    explanationText = getRes("turn_calculation_note_for_library");
                }
            }

            $("#segmentationModeThreshold_" + dashType).find("#segmentationMode_labelLow").text(labelLow);
            $("#segmentationModeThreshold_" + dashType).find("#segmentationMode_inputLower").val(inputLower);
            $("#segmentationModeThreshold_" + dashType).find("#segmentationMode_labelMedium").text(labelMedium);
            $("#segmentationModeThreshold_" + dashType).find("#segmentationMode_inputHigher").val(inputHigher);
            $("#segmentationModeThreshold_" + dashType).find("#segmentationMode_labelHigh").text(labelHigh);
            $("#segmentationModeThreshold_" + dashType).find("#thresholdNote").text(explanationText);

            if(EdelweissAnalytics.filterOptions[dashType].includeTitlesFromAllStockAnalysisClasses) {
                EdelweissAnalytics.sortColumnNameForAllTitles[dashType] = sortColumnName;
                EdelweissAnalytics.sortDirectionForAllTitles[dashType] = sortDirection;
            } else {
                EdelweissAnalytics.sortDirectionByStockAnalysisClass[dashType][stockAnalysisClass] = sortDirection;
                EdelweissAnalytics.sortColumnNameByStockAnalysisClass[dashType][stockAnalysisClass] = sortColumnName;
            }
        });
    };

    this.saveSegmentationThresholds = function (dashType, inventoryLowerName, inventoryUpperName, turnLowerName, turnUpperName) {
        var doneSavingThresholds = new Promise(function (resolve, reject) {
            var alertIt = "0";
            var modeThresholdLower = $("#segmentationModeThreshold_" + dashType).find("#segmentationMode_inputLower").val();
            var modeThresholdUpper = $("#segmentationModeThreshold_" + dashType).find("#segmentationMode_inputHigher").val();
            if (!$.isNumeric(modeThresholdLower) || !$.isNumeric(modeThresholdUpper)) {
                alertIt = "You must enter numbers in these fields";
            } else {
                if (modeThresholdLower * 1 >= modeThresholdUpper * 1) {
                    alertIt = "The low value must be less than the high value";
                }
            }

            var prefNameLower = "";
            var prefNameUpper = "";

            if (EdelweissAnalytics.filterOptions[dashType].segmentationMode === EdelweissAnalytics.segmentationModes.Activity) {
                prefNameLower = inventoryLowerName;
                prefNameUpper = inventoryUpperName;
            } else if (EdelweissAnalytics.filterOptions[dashType].segmentationMode === EdelweissAnalytics.segmentationModes.Turn) {
                prefNameLower = turnLowerName;
                prefNameUpper = turnUpperName;
            }

            if (alertIt == "0") {
                async.parallel([
                    async.apply(EdelweissAnalytics.saveAnalyticsUserPreference, prefNameLower, modeThresholdLower),
                    async.apply(EdelweissAnalytics.saveAnalyticsUserPreference, prefNameUpper, modeThresholdUpper)
                ], function (err, results) {
                    if (err) {
                        console.warn(err.message);
                        reject(err.message);
                    } else {
                        if (EdelweissAnalytics.filterOptions[dashType].segmentationMode === EdelweissAnalytics.segmentationModes.Activity) {
                            if (EdelweissAnalytics.filterOptions[dashType].inventoryIndexRangeLower != parseInt(modeThresholdLower) || EdelweissAnalytics.filterOptions[dashType].inventoryIndexRangeUpper != parseInt(modeThresholdUpper)) {
                                EdelweissAnalytics.laneSettingChange[dashType] = true;
                            }
                            EdelweissAnalytics.filterOptions[dashType].inventoryIndexRangeLower = modeThresholdLower;
                            EdelweissAnalytics.filterOptions[dashType].inventoryIndexRangeUpper = modeThresholdUpper;
                        } else if (EdelweissAnalytics.filterOptions[dashType].segmentationMode === EdelweissAnalytics.segmentationModes.Turn) {
                            if (EdelweissAnalytics.filterOptions[dashType].turnRangeLower != parseFloat(modeThresholdLower) || EdelweissAnalytics.filterOptions[dashType].turnRangeUpper != parseFloat(modeThresholdUpper)) {
                                EdelweissAnalytics.laneSettingChange[dashType] = true;
                            }
                            EdelweissAnalytics.filterOptions[dashType].turnRangeLower = modeThresholdLower;
                            EdelweissAnalytics.filterOptions[dashType].turnRangeUpper = modeThresholdUpper;
                        }
                        resolve();
                    }
                });                
            } else {
                alert(alertIt);
                resolve();
            }
        });

        return doneSavingThresholds;
    }

    this.savePubDateRange = function (dashType, prefPubDateLowerBound, prefPubDateUpperBound) {
        var doneSavingPubDateRange = new Promise(function (resolve, reject) {
            var fromElement = "#pubDateLowerBound";
            var toElement = "#pubDateUpperBound";

            var fromDate = $(fromElement).datepicker("getDate");
            var fromDateString = fromDate.toISOString();

            if ($("#includeUpperBoundCheckBox").prop("checked")) {
                var toDate = $(toElement).datepicker("getDate");
                var toDateString = toDate.toISOString();
            } else {
                var toDateString = "*";
            }

            EdelweissAnalytics.filterOptions[dashType].pubDateFilter = "[" + fromDateString + " TO " + toDateString + "]";
            async.parallel([
                async.apply(EdelweissAnalytics.saveAnalyticsUserPreference, prefPubDateLowerBound, fromDateString),
                async.apply(EdelweissAnalytics.saveAnalyticsUserPreference, prefPubDateUpperBound, toDateString)
            ], function (err, results) {
                if (err) {
                    console.warn(err.message);
                    resolve(err.message);
                } else {
                    resolve();
                }
            });
        });
        return doneSavingPubDateRange;
    }

    this.finalActions = function (dashType, laneKey) {
        if (this.tempCategoryExist) {
            var temporaryFilterPathLength = EdelweissAnalytics.temporaryCategoryFilterPath.length;
            var currentCatName = EdelweissAnalytics.temporaryCategoryFilterPath[temporaryFilterPathLength - 1].categoryName;
            EdelweissAnalytics.removeTempCategoryFilterAndUpdateAllLanes(currentCatName);
            EdelweissAnalytics.toggleSelectionOfCategoryInLeftNav(this.previousCategoryTypeForComparison, currentCatName, false);
        }

        if (EdelweissAnalytics.laneSettingChange[dashType]) {
            logPageHit(getEnumValue("siteContext", "EDELWEISSANALYTICS"),
                EdelweissAnalytics.getSiteAreaFromLaneKey(laneKey), EdelweissAnalytics.sessionId);
            EdelweissAnalytics.startLaneUpdateProcess(laneKey);
        }
    };

    this.hideLanePreferenceActions = function (laneKey, dashType, isSegmentationThresholdIncluded,
        isPubDateRangeIncluded, inventoryLowerName, inventoryUpperName, turnLowerName, turnUpperName,
        pubDateLowerBoundName, pubDateUpperBoundName) {

        var _this = this;
        var savingPreferencesPromises = [];  
        if (isSegmentationThresholdIncluded) {
            var doneSavingSegmentationThresholds = _this.saveSegmentationThresholds(dashType, inventoryLowerName,
                inventoryUpperName, turnLowerName, turnUpperName);
            savingPreferencesPromises.push(doneSavingSegmentationThresholds);
        }
        if (isPubDateRangeIncluded) {
            var doneSavingPubDateRange = _this.savePubDateRange(dashType, pubDateLowerBoundName, pubDateUpperBoundName);
            savingPreferencesPromises.push(doneSavingPubDateRange);
        }
        Promise.all(savingPreferencesPromises).then(function () {
            _this.finalActions(dashType, laneKey);
        });
    };
})();;
/*! TableSorter (FORK) v2.31.0 *//*
* Client-side table sorting with ease!
* @requires jQuery v1.2.6+
*
* Copyright (c) 2007 Christian Bach
* fork maintained by Rob Garrison
*
* Examples and original docs at: http://tablesorter.com
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* @type jQuery
* @name tablesorter (FORK)
* @cat Plugins/Tablesorter
* @author Christian Bach - christian.bach@polyester.se
* @contributor Rob Garrison - https://github.com/Mottie/tablesorter
* @docs (fork) - https://mottie.github.io/tablesorter/docs/
*/
/*jshint browser:true, jquery:true, unused:false, expr: true */
; (function ($) {
    'use strict';
    var ts = $.tablesorter = {

        version: '2.31.0',

        parsers: [],
        widgets: [],
        defaults: {

            // *** appearance
            theme: 'default',  // adds tablesorter-{theme} to the table for styling
            widthFixed: false,      // adds colgroup to fix widths of columns
            showProcessing: false,      // show an indeterminate timer icon in the header when the table is sorted or filtered.

            headerTemplate: '{content}',// header layout template (HTML ok); {content} = innerHTML, {icon} = <i/> // class from cssIcon
            onRenderTemplate: null,       // function( index, template ) { return template; }, // template is a string
            onRenderHeader: null,       // function( index ) {}, // nothing to return

            // *** functionality
            cancelSelection: true,       // prevent text selection in the header
            tabIndex: true,       // add tabindex to header for keyboard accessibility
            dateFormat: 'mmddyyyy', // other options: 'ddmmyyy' or 'yyyymmdd'
            sortMultiSortKey: 'shiftKey', // key used to select additional columns
            sortResetKey: 'ctrlKey',  // key used to remove sorting on a column
            usNumberFormat: true,       // false for German '1.234.567,89' or French '1 234 567,89'
            delayInit: false,      // if false, the parsed table contents will not update until the first sort
            serverSideSorting: false,      // if true, server-side sorting should be performed because client-side sorting will be disabled, but the ui and events will still be used.
            resort: true,       // default setting to trigger a resort after an 'update', 'addRows', 'updateCell', etc has completed

            // *** sort options
            headers: {},         // set sorter, string, empty, locked order, sortInitialOrder, filter, etc.
            ignoreCase: true,       // ignore case while sorting
            sortForce: null,       // column(s) first sorted; always applied
            sortList: [],         // Initial sort order; applied initially; updated when manually sorted
            sortAppend: null,       // column(s) sorted last; always applied
            sortStable: false,      // when sorting two rows with exactly the same content, the original sort order is maintained

            sortInitialOrder: 'asc',      // sort direction on first click
            sortLocaleCompare: false,      // replace equivalent character (accented characters)
            sortReset: false,      // third click on the header will reset column to default - unsorted
            sortRestart: false,      // restart sort to 'sortInitialOrder' when clicking on previously unsorted columns

            emptyTo: 'bottom',   // sort empty cell to bottom, top, none, zero, emptyMax, emptyMin
            stringTo: 'max',      // sort strings in numerical column as max, min, top, bottom, zero
            duplicateSpan: true,       // colspan cells in the tbody will have duplicated content in the cache for each spanned column
            textExtraction: 'basic',    // text extraction method/function - function( node, table, cellIndex ) {}
            textAttribute: 'data-text',// data-attribute that contains alternate cell text (used in default textExtraction function)
            textSorter: null,       // choose overall or specific column sorter function( a, b, direction, table, columnIndex ) [alt: ts.sortText]
            numberSorter: null,       // choose overall numeric sorter function( a, b, direction, maxColumnValue )

            // *** widget options
            initWidgets: true,       // apply widgets on tablesorter initialization
            widgetClass: 'widget-{name}', // table class name template to match to include a widget
            widgets: [],         // method to add widgets, e.g. widgets: ['zebra']
            widgetOptions: {
                zebra: ['even', 'odd']  // zebra widget alternating row class names
            },

            // *** callbacks
            initialized: null,       // function( table ) {},

            // *** extra css class names
            tableClass: '',
            cssAsc: '',
            cssDesc: '',
            cssNone: '',
            cssHeader: '',
            cssHeaderRow: '',
            cssProcessing: '', // processing icon applied to header during sort/filter

            cssChildRow: 'tablesorter-childRow', // class name indiciating that a row is to be attached to its parent
            cssInfoBlock: 'tablesorter-infoOnly', // don't sort tbody with this class name (only one class name allowed here!)
            cssNoSort: 'tablesorter-noSort',   // class name added to element inside header; clicking on it won't cause a sort
            cssIgnoreRow: 'tablesorter-ignoreRow',// header row to ignore; cells within this row will not be added to c.$headers

            cssIcon: 'tablesorter-icon', // if this class does not exist, the {icon} will not be added from the headerTemplate
            cssIconNone: '', // class name added to the icon when there is no column sort
            cssIconAsc: '', // class name added to the icon when the column has an ascending sort
            cssIconDesc: '', // class name added to the icon when the column has a descending sort
            cssIconDisabled: '', // class name added to the icon when the column has a disabled sort

            // *** events
            pointerClick: 'click',
            pointerDown: 'mousedown',
            pointerUp: 'mouseup',

            // *** selectors
            selectorHeaders: '> thead th, > thead td',
            selectorSort: 'th, td', // jQuery selector of content within selectorHeaders that is clickable to trigger a sort
            selectorRemove: '.remove-me',

            // *** advanced
            debug: false,

            // *** Internal variables
            headerList: [],
            empties: {},
            strings: {},
            parsers: [],

            // *** parser options for validator; values must be falsy!
            globalize: 0,
            imgAttr: 0

            // removed: widgetZebra: { css: ['even', 'odd'] }

        },

        // internal css classes - these will ALWAYS be added to
        // the table and MUST only contain one class name - fixes #381
        css: {
            table: 'tablesorter',
            cssHasChild: 'tablesorter-hasChildRow',
            childRow: 'tablesorter-childRow',
            colgroup: 'tablesorter-colgroup',
            header: 'tablesorter-header',
            headerRow: 'tablesorter-headerRow',
            headerIn: 'tablesorter-header-inner',
            icon: 'tablesorter-icon',
            processing: 'tablesorter-processing',
            sortAsc: 'tablesorter-headerAsc',
            sortDesc: 'tablesorter-headerDesc',
            sortNone: 'tablesorter-headerUnSorted'
        },

        // labels applied to sortable headers for accessibility (aria) support
        language: {
            sortAsc: 'Ascending sort applied, ',
            sortDesc: 'Descending sort applied, ',
            sortNone: 'No sort applied, ',
            sortDisabled: 'sorting is disabled',
            nextAsc: 'activate to apply an ascending sort',
            nextDesc: 'activate to apply a descending sort',
            nextNone: 'activate to remove the sort'
        },

        regex: {
            templateContent: /\{content\}/g,
            templateIcon: /\{icon\}/g,
            templateName: /\{name\}/i,
            spaces: /\s+/g,
            nonWord: /\W/g,
            formElements: /(input|select|button|textarea)/i,

            // *** sort functions ***
            // regex used in natural sort
            // chunk/tokenize numbers & letters
            chunk: /(^([+\-]?(?:\d*)(?:\.\d*)?(?:[eE][+\-]?\d+)?)?$|^0x[0-9a-f]+$|\d+)/gi,
            // replace chunks @ ends
            chunks: /(^\\0|\\0$)/,
            hex: /^0x[0-9a-f]+$/i,

            // *** formatFloat ***
            comma: /,/g,
            digitNonUS: /[\s|\.]/g,
            digitNegativeTest: /^\s*\([.\d]+\)/,
            digitNegativeReplace: /^\s*\(([.\d]+)\)/,

            // *** isDigit ***
            digitTest: /^[\-+(]?\d+[)]?$/,
            digitReplace: /[,.'"\s]/g

        },

        // digit sort, text location
        string: {
            max: 1,
            min: -1,
            emptymin: 1,
            emptymax: -1,
            zero: 0,
            none: 0,
            'null': 0,
            top: true,
            bottom: false
        },

        keyCodes: {
            enter: 13
        },

        // placeholder date parser data (globalize)
        dates: {},

        // These methods can be applied on table.config instance
        instanceMethods: {},

		/*
		▄█████ ██████ ██████ ██  ██ █████▄
		▀█▄    ██▄▄     ██   ██  ██ ██▄▄██
		   ▀█▄ ██▀▀     ██   ██  ██ ██▀▀▀
		█████▀ ██████   ██   ▀████▀ ██
		*/

        setup: function (table, c) {
            // if no thead or tbody, or tablesorter is already present, quit
            if (!table || !table.tHead || table.tBodies.length === 0 || table.hasInitialized === true) {
                if (ts.debug(c, 'core')) {
                    if (table.hasInitialized) {
                        console.warn('Stopping initialization. Tablesorter has already been initialized');
                    } else {
                        console.error('Stopping initialization! No table, thead or tbody', table);
                    }
                }
                return;
            }

            var tmp = '',
                $table = $(table),
                meta = $.metadata;
            // initialization flag
            table.hasInitialized = false;
            // table is being processed flag
            table.isProcessing = true;
            // make sure to store the config object
            table.config = c;
            // save the settings where they read
            $.data(table, 'tablesorter', c);
            if (ts.debug(c, 'core')) {
                console[console.group ? 'group' : 'log']('Initializing tablesorter v' + ts.version);
                $.data(table, 'startoveralltimer', new Date());
            }

            // removing this in version 3 (only supports jQuery 1.7+)
            c.supportsDataObject = (function (version) {
                version[0] = parseInt(version[0], 10);
                return (version[0] > 1) || (version[0] === 1 && parseInt(version[1], 10) >= 4);
            })($.fn.jquery.split('.'));
            // ensure case insensitivity
            c.emptyTo = c.emptyTo.toLowerCase();
            c.stringTo = c.stringTo.toLowerCase();
            c.last = { sortList: [], clickedIndex: -1 };
            // add table theme class only if there isn't already one there
            if (!/tablesorter\-/.test($table.attr('class'))) {
                tmp = (c.theme !== '' ? ' tablesorter-' + c.theme : '');
            }

            // give the table a unique id, which will be used in namespace binding
            if (!c.namespace) {
                c.namespace = '.tablesorter' + Math.random().toString(16).slice(2);
            } else {
                // make sure namespace starts with a period & doesn't have weird characters
                c.namespace = '.' + c.namespace.replace(ts.regex.nonWord, '');
            }

            c.table = table;
            c.$table = $table
                // add namespace to table to allow bindings on extra elements to target
                // the parent table (e.g. parser-input-select)
                .addClass(ts.css.table + ' ' + c.tableClass + tmp + ' ' + c.namespace.slice(1))
                .attr('role', 'grid');
            c.$headers = $table.find(c.selectorHeaders);

            c.$table.children().children('tr').attr('role', 'row');
            c.$tbodies = $table.children('tbody:not(.' + c.cssInfoBlock + ')').attr({
                'aria-live': 'polite',
                'aria-relevant': 'all'
            });
            if (c.$table.children('caption').length) {
                tmp = c.$table.children('caption')[0];
                if (!tmp.id) { tmp.id = c.namespace.slice(1) + 'caption'; }
                c.$table.attr('aria-labelledby', tmp.id);
            }
            c.widgetInit = {}; // keep a list of initialized widgets
            // change textExtraction via data-attribute
            c.textExtraction = c.$table.attr('data-text-extraction') || c.textExtraction || 'basic';
            // build headers
            ts.buildHeaders(c);
            // fixate columns if the users supplies the fixedWidth option
            // do this after theme has been applied
            ts.fixColumnWidth(table);
            // add widgets from class name
            ts.addWidgetFromClass(table);
            // add widget options before parsing (e.g. grouping widget has parser settings)
            ts.applyWidgetOptions(table);
            // try to auto detect column type, and store in tables config
            ts.setupParsers(c);
            // start total row count at zero
            c.totalRows = 0;
            // only validate options while debugging. See #1528
            if (c.debug) {
                ts.validateOptions(c);
            }
            // build the cache for the tbody cells
            // delayInit will delay building the cache until the user starts a sort
            if (!c.delayInit) { ts.buildCache(c); }
            // bind all header events and methods
            ts.bindEvents(table, c.$headers, true);
            ts.bindMethods(c);
            // get sort list from jQuery data or metadata
            // in jQuery < 1.4, an error occurs when calling $table.data()
            if (c.supportsDataObject && typeof $table.data().sortlist !== 'undefined') {
                c.sortList = $table.data().sortlist;
            } else if (meta && ($table.metadata() && $table.metadata().sortlist)) {
                c.sortList = $table.metadata().sortlist;
            }
            // apply widget init code
            ts.applyWidget(table, true);
            // if user has supplied a sort list to constructor
            if (c.sortList.length > 0) {
                // save sortList before any sortAppend is added
                c.last.sortList = c.sortList;
                ts.sortOn(c, c.sortList, {}, !c.initWidgets);
            } else {
                ts.setHeadersCss(c);
                if (c.initWidgets) {
                    // apply widget format
                    ts.applyWidget(table, false);
                }
            }

            // show processesing icon
            if (c.showProcessing) {
                $table
                    .unbind('sortBegin' + c.namespace + ' sortEnd' + c.namespace)
                    .bind('sortBegin' + c.namespace + ' sortEnd' + c.namespace, function (e) {
                        clearTimeout(c.timerProcessing);
                        ts.isProcessing(table);
                        if (e.type === 'sortBegin') {
                            c.timerProcessing = setTimeout(function () {
                                ts.isProcessing(table, true);
                            }, 500);
                        }
                    });
            }

            // initialized
            table.hasInitialized = true;
            table.isProcessing = false;
            if (ts.debug(c, 'core')) {
                console.log('Overall initialization time:' + ts.benchmark($.data(table, 'startoveralltimer')));
                if (ts.debug(c, 'core') && console.groupEnd) { console.groupEnd(); }
            }
            $table.triggerHandler('tablesorter-initialized', table);
            if (typeof c.initialized === 'function') {
                c.initialized(table);
            }
        },

        bindMethods: function (c) {
            var $table = c.$table,
                namespace = c.namespace,
                events = ('sortReset update updateRows updateAll updateHeaders addRows updateCell updateComplete ' +
                    'sorton appendCache updateCache applyWidgetId applyWidgets refreshWidgets destroy mouseup ' +
                    'mouseleave ').split(' ')
                    .join(namespace + ' ');
            // apply easy methods that trigger bound events
            $table
                .unbind(events.replace(ts.regex.spaces, ' '))
                .bind('sortReset' + namespace, function (e, callback) {
                    e.stopPropagation();
                    // using this.config to ensure functions are getting a non-cached version of the config
                    ts.sortReset(this.config, function (table) {
                        if (table.isApplyingWidgets) {
                            // multiple triggers in a row... filterReset, then sortReset - see #1361
                            // wait to update widgets
                            setTimeout(function () {
                                ts.applyWidget(table, '', callback);
                            }, 100);
                        } else {
                            ts.applyWidget(table, '', callback);
                        }
                    });
                })
                .bind('updateAll' + namespace, function (e, resort, callback) {
                    e.stopPropagation();
                    ts.updateAll(this.config, resort, callback);
                })
                .bind('update' + namespace + ' updateRows' + namespace, function (e, resort, callback) {
                    e.stopPropagation();
                    ts.update(this.config, resort, callback);
                })
                .bind('updateHeaders' + namespace, function (e, callback) {
                    e.stopPropagation();
                    ts.updateHeaders(this.config, callback);
                })
                .bind('updateCell' + namespace, function (e, cell, resort, callback) {
                    e.stopPropagation();
                    ts.updateCell(this.config, cell, resort, callback);
                })
                .bind('addRows' + namespace, function (e, $row, resort, callback) {
                    e.stopPropagation();
                    ts.addRows(this.config, $row, resort, callback);
                })
                .bind('updateComplete' + namespace, function () {
                    this.isUpdating = false;
                })
                .bind('sorton' + namespace, function (e, list, callback, init) {
                    e.stopPropagation();
                    ts.sortOn(this.config, list, callback, init);
                })
                .bind('appendCache' + namespace, function (e, callback, init) {
                    e.stopPropagation();
                    ts.appendCache(this.config, init);
                    if ($.isFunction(callback)) {
                        callback(this);
                    }
                })
                // $tbodies variable is used by the tbody sorting widget
                .bind('updateCache' + namespace, function (e, callback, $tbodies) {
                    e.stopPropagation();
                    ts.updateCache(this.config, callback, $tbodies);
                })
                .bind('applyWidgetId' + namespace, function (e, id) {
                    e.stopPropagation();
                    ts.applyWidgetId(this, id);
                })
                .bind('applyWidgets' + namespace, function (e, callback) {
                    e.stopPropagation();
                    // apply widgets (false = not initializing)
                    ts.applyWidget(this, false, callback);
                })
                .bind('refreshWidgets' + namespace, function (e, all, dontapply) {
                    e.stopPropagation();
                    ts.refreshWidgets(this, all, dontapply);
                })
                .bind('removeWidget' + namespace, function (e, name, refreshing) {
                    e.stopPropagation();
                    ts.removeWidget(this, name, refreshing);
                })
                .bind('destroy' + namespace, function (e, removeClasses, callback) {
                    e.stopPropagation();
                    ts.destroy(this, removeClasses, callback);
                })
                .bind('resetToLoadState' + namespace, function (e) {
                    e.stopPropagation();
                    // remove all widgets
                    ts.removeWidget(this, true, false);
                    var tmp = $.extend(true, {}, c.originalSettings);
                    // restore original settings; this clears out current settings, but does not clear
                    // values saved to storage.
                    c = $.extend(true, {}, ts.defaults, tmp);
                    c.originalSettings = tmp;
                    this.hasInitialized = false;
                    // setup the entire table again
                    ts.setup(this, c);
                });
        },

        bindEvents: function (table, $headers, core) {
            table = $(table)[0];
            var tmp,
                c = table.config,
                namespace = c.namespace,
                downTarget = null;
            if (core !== true) {
                $headers.addClass(namespace.slice(1) + '_extra_headers');
                tmp = ts.getClosest($headers, 'table');
                if (tmp.length && tmp[0].nodeName === 'TABLE' && tmp[0] !== table) {
                    $(tmp[0]).addClass(namespace.slice(1) + '_extra_table');
                }
            }
            tmp = (c.pointerDown + ' ' + c.pointerUp + ' ' + c.pointerClick + ' sort keyup ')
                .replace(ts.regex.spaces, ' ')
                .split(' ')
                .join(namespace + ' ');
            // apply event handling to headers and/or additional headers (stickyheaders, scroller, etc)
            $headers
                // http://stackoverflow.com/questions/5312849/jquery-find-self;
                .find(c.selectorSort)
                .add($headers.filter(c.selectorSort))
                .unbind(tmp)
                .bind(tmp, function (e, external) {
                    var $cell, cell, temp,
                        $target = $(e.target),
                        // wrap event type in spaces, so the match doesn't trigger on inner words
                        type = ' ' + e.type + ' ';
                    // only recognize left clicks
                    if (((e.which || e.button) !== 1 && !type.match(' ' + c.pointerClick + ' | sort | keyup ')) ||
                        // allow pressing enter
                        (type === ' keyup ' && e.which !== ts.keyCodes.enter) ||
                        // allow triggering a click event (e.which is undefined) & ignore physical clicks
                        (type.match(' ' + c.pointerClick + ' ') && typeof e.which !== 'undefined')) {
                        return;
                    }
                    // ignore mouseup if mousedown wasn't on the same target
                    if (type.match(' ' + c.pointerUp + ' ') && downTarget !== e.target && external !== true) {
                        return;
                    }
                    // set target on mousedown
                    if (type.match(' ' + c.pointerDown + ' ')) {
                        downTarget = e.target;
                        // preventDefault needed or jQuery v1.3.2 and older throws an
                        // "Uncaught TypeError: handler.apply is not a function" error
                        temp = $target.jquery.split('.');
                        if (temp[0] === '1' && temp[1] < 4) { e.preventDefault(); }
                        return;
                    }
                    downTarget = null;
                    $cell = ts.getClosest($(this), '.' + ts.css.header);
                    // prevent sort being triggered on form elements
                    if (ts.regex.formElements.test(e.target.nodeName) ||
                        // nosort class name, or elements within a nosort container
                        $target.hasClass(c.cssNoSort) || $target.parents('.' + c.cssNoSort).length > 0 ||
                        // disabled cell directly clicked
                        $cell.hasClass('sorter-false') ||
                        // elements within a button
                        $target.parents('button').length > 0) {
                        return !c.cancelSelection;
                    }
                    if (c.delayInit && ts.isEmptyObject(c.cache)) {
                        ts.buildCache(c);
                    }
                    // use column index from data-attribute or index of current row; fixes #1116
                    c.last.clickedIndex = $cell.attr('data-column') || $cell.index();
                    cell = c.$headerIndexed[c.last.clickedIndex][0];
                    if (cell && !cell.sortDisabled) {
                        ts.initSort(c, cell, e);
                    }
                });
            if (c.cancelSelection) {
                // cancel selection
                $headers
                    .attr('unselectable', 'on')
                    .bind('selectstart', false)
                    .css({
                        'user-select': 'none',
                        'MozUserSelect': 'none' // not needed for jQuery 1.8+
                    });
            }
        },

        buildHeaders: function (c) {
            var $temp, icon, timer, indx;
            c.headerList = [];
            c.headerContent = [];
            c.sortVars = [];
            if (ts.debug(c, 'core')) {
                timer = new Date();
            }
            // children tr in tfoot - see issue #196 & #547
            // don't pass table.config to computeColumnIndex here - widgets (math) pass it to "quickly" index tbody cells
            c.columns = ts.computeColumnIndex(c.$table.children('thead, tfoot').children('tr'));
            // add icon if cssIcon option exists
            icon = c.cssIcon ?
                '<i class="' + (c.cssIcon === ts.css.icon ? ts.css.icon : c.cssIcon + ' ' + ts.css.icon) + '"></i>' :
                '';
            // redefine c.$headers here in case of an updateAll that replaces or adds an entire header cell - see #683
            c.$headers = $($.map(c.$table.find(c.selectorHeaders), function (elem, index) {
                var configHeaders, header, column, template, tmp,
                    $elem = $(elem);
                // ignore cell (don't add it to c.$headers) if row has ignoreRow class
                if (ts.getClosest($elem, 'tr').hasClass(c.cssIgnoreRow)) { return; }
                // transfer data-column to element if not th/td - #1459
                if (!/(th|td)/i.test(elem.nodeName)) {
                    tmp = ts.getClosest($elem, 'th, td');
                    $elem.attr('data-column', tmp.attr('data-column'));
                }
                // make sure to get header cell & not column indexed cell
                configHeaders = ts.getColumnData(c.table, c.headers, index, true);
                // save original header content
                c.headerContent[index] = $elem.html();
                // if headerTemplate is empty, don't reformat the header cell
                if (c.headerTemplate !== '' && !$elem.find('.' + ts.css.headerIn).length) {
                    // set up header template
                    template = c.headerTemplate
                        .replace(ts.regex.templateContent, $elem.html())
                        .replace(ts.regex.templateIcon, $elem.find('.' + ts.css.icon).length ? '' : icon);
                    if (c.onRenderTemplate) {
                        header = c.onRenderTemplate.apply($elem, [index, template]);
                        // only change t if something is returned
                        if (header && typeof header === 'string') {
                            template = header;
                        }
                    }
                    $elem.html('<div class="' + ts.css.headerIn + '">' + template + '</div>'); // faster than wrapInner
                }
                if (c.onRenderHeader) {
                    c.onRenderHeader.apply($elem, [index, c, c.$table]);
                }
                column = parseInt($elem.attr('data-column'), 10);
                elem.column = column;
                tmp = ts.getOrder(ts.getData($elem, configHeaders, 'sortInitialOrder') || c.sortInitialOrder);
                // this may get updated numerous times if there are multiple rows
                c.sortVars[column] = {
                    count: -1, // set to -1 because clicking on the header automatically adds one
                    order: tmp ?
                        (c.sortReset ? [1, 0, 2] : [1, 0]) : // desc, asc, unsorted
                        (c.sortReset ? [0, 1, 2] : [0, 1]),  // asc, desc, unsorted
                    lockedOrder: false,
                    sortedBy: ''
                };
                tmp = ts.getData($elem, configHeaders, 'lockedOrder') || false;
                if (typeof tmp !== 'undefined' && tmp !== false) {
                    c.sortVars[column].lockedOrder = true;
                    c.sortVars[column].order = ts.getOrder(tmp) ? [1, 1] : [0, 0];
                }
                // add cell to headerList
                c.headerList[index] = elem;
                $elem.addClass(ts.css.header + ' ' + c.cssHeader);
                // add to parent in case there are multiple rows
                ts.getClosest($elem, 'tr')
                    .addClass(ts.css.headerRow + ' ' + c.cssHeaderRow)
                    .attr('role', 'row');
                // allow keyboard cursor to focus on element
                if (c.tabIndex) {
                    $elem.attr('tabindex', 0);
                }
                return elem;
            }));
            // cache headers per column
            c.$headerIndexed = [];
            for (indx = 0; indx < c.columns; indx++) {
                // colspan in header making a column undefined
                if (ts.isEmptyObject(c.sortVars[indx])) {
                    c.sortVars[indx] = {};
                }
                // Use c.$headers.parent() in case selectorHeaders doesn't point to the th/td
                $temp = c.$headers.filter('[data-column="' + indx + '"]');
                // target sortable column cells, unless there are none, then use non-sortable cells
                // .last() added in jQuery 1.4; use .filter(':last') to maintain compatibility with jQuery v1.2.6
                c.$headerIndexed[indx] = $temp.length ?
                    $temp.not('.sorter-false').length ?
                        $temp.not('.sorter-false').filter(':last') :
                        $temp.filter(':last') :
                    $();
            }
            c.$table.find(c.selectorHeaders).attr({
                scope: 'col',
                role: 'columnheader'
            });
            // enable/disable sorting
            ts.updateHeader(c);
            if (ts.debug(c, 'core')) {
                console.log('Built headers:' + ts.benchmark(timer));
                console.log(c.$headers);
            }
        },

        // Use it to add a set of methods to table.config which will be available for all tables.
        // This should be done before table initialization
        addInstanceMethods: function (methods) {
            $.extend(ts.instanceMethods, methods);
        },

		/*
		█████▄ ▄████▄ █████▄ ▄█████ ██████ █████▄ ▄█████
		██▄▄██ ██▄▄██ ██▄▄██ ▀█▄    ██▄▄   ██▄▄██ ▀█▄
		██▀▀▀  ██▀▀██ ██▀██     ▀█▄ ██▀▀   ██▀██     ▀█▄
		██     ██  ██ ██  ██ █████▀ ██████ ██  ██ █████▀
		*/
        setupParsers: function (c, $tbodies) {
            var rows, list, span, max, colIndex, indx, header, configHeaders,
                noParser, parser, extractor, time, tbody, len,
                table = c.table,
                tbodyIndex = 0,
                debug = ts.debug(c, 'core'),
                debugOutput = {};
            // update table bodies in case we start with an empty table
            c.$tbodies = c.$table.children('tbody:not(.' + c.cssInfoBlock + ')');
            tbody = typeof $tbodies === 'undefined' ? c.$tbodies : $tbodies;
            len = tbody.length;
            if (len === 0) {
                return debug ? console.warn('Warning: *Empty table!* Not building a parser cache') : '';
            } else if (debug) {
                time = new Date();
                console[console.group ? 'group' : 'log']('Detecting parsers for each column');
            }
            list = {
                extractors: [],
                parsers: []
            };
            while (tbodyIndex < len) {
                rows = tbody[tbodyIndex].rows;
                if (rows.length) {
                    colIndex = 0;
                    max = c.columns;
                    for (indx = 0; indx < max; indx++) {
                        header = c.$headerIndexed[colIndex];
                        if (header && header.length) {
                            // get column indexed table cell; adding true parameter fixes #1362 but
                            // it would break backwards compatibility...
                            configHeaders = ts.getColumnData(table, c.headers, colIndex); // , true );
                            // get column parser/extractor
                            extractor = ts.getParserById(ts.getData(header, configHeaders, 'extractor'));
                            parser = ts.getParserById(ts.getData(header, configHeaders, 'sorter'));
                            noParser = ts.getData(header, configHeaders, 'parser') === 'false';
                            // empty cells behaviour - keeping emptyToBottom for backwards compatibility
                            c.empties[colIndex] = (
                                ts.getData(header, configHeaders, 'empty') ||
                                c.emptyTo || (c.emptyToBottom ? 'bottom' : 'top')).toLowerCase();
                            // text strings behaviour in numerical sorts
                            c.strings[colIndex] = (
                                ts.getData(header, configHeaders, 'string') ||
                                c.stringTo ||
                                'max').toLowerCase();
                            if (noParser) {
                                parser = ts.getParserById('no-parser');
                            }
                            if (!extractor) {
                                // For now, maybe detect someday
                                extractor = false;
                            }
                            if (!parser) {
                                parser = ts.detectParserForColumn(c, rows, -1, colIndex);
                            }
                            if (debug) {
                                debugOutput['(' + colIndex + ') ' + header.text()] = {
                                    parser: parser.id,
                                    extractor: extractor ? extractor.id : 'none',
                                    string: c.strings[colIndex],
                                    empty: c.empties[colIndex]
                                };
                            }
                            list.parsers[colIndex] = parser;
                            list.extractors[colIndex] = extractor;
                            span = header[0].colSpan - 1;
                            if (span > 0) {
                                colIndex += span;
                                max += span;
                                while (span + 1 > 0) {
                                    // set colspan columns to use the same parsers & extractors
                                    list.parsers[colIndex - span] = parser;
                                    list.extractors[colIndex - span] = extractor;
                                    span--;
                                }
                            }
                        }
                        colIndex++;
                    }
                }
                tbodyIndex += (list.parsers.length) ? len : 1;
            }
            if (debug) {
                if (!ts.isEmptyObject(debugOutput)) {
                    console[console.table ? 'table' : 'log'](debugOutput);
                } else {
                    console.warn('  No parsers detected!');
                }
                console.log('Completed detecting parsers' + ts.benchmark(time));
                if (console.groupEnd) { console.groupEnd(); }
            }
            c.parsers = list.parsers;
            c.extractors = list.extractors;
        },

        addParser: function (parser) {
            var indx,
                len = ts.parsers.length,
                add = true;
            for (indx = 0; indx < len; indx++) {
                if (ts.parsers[indx].id.toLowerCase() === parser.id.toLowerCase()) {
                    add = false;
                }
            }
            if (add) {
                ts.parsers[ts.parsers.length] = parser;
            }
        },

        getParserById: function (name) {
            /*jshint eqeqeq:false */ // eslint-disable-next-line eqeqeq
            if (name == 'false') { return false; }
            var indx,
                len = ts.parsers.length;
            for (indx = 0; indx < len; indx++) {
                if (ts.parsers[indx].id.toLowerCase() === (name.toString()).toLowerCase()) {
                    return ts.parsers[indx];
                }
            }
            return false;
        },

        detectParserForColumn: function (c, rows, rowIndex, cellIndex) {
            var cur, $node, row,
                indx = ts.parsers.length,
                node = false,
                nodeValue = '',
                debug = ts.debug(c, 'core'),
                keepLooking = true;
            while (nodeValue === '' && keepLooking) {
                rowIndex++;
                row = rows[rowIndex];
                // stop looking after 50 empty rows
                if (row && rowIndex < 50) {
                    if (row.className.indexOf(ts.cssIgnoreRow) < 0) {
                        node = rows[rowIndex].cells[cellIndex];
                        nodeValue = ts.getElementText(c, node, cellIndex);
                        $node = $(node);
                        if (debug) {
                            console.log('Checking if value was empty on row ' + rowIndex + ', column: ' +
                                cellIndex + ': "' + nodeValue + '"');
                        }
                    }
                } else {
                    keepLooking = false;
                }
            }
            while (--indx >= 0) {
                cur = ts.parsers[indx];
                // ignore the default text parser because it will always be true
                if (cur && cur.id !== 'text' && cur.is && cur.is(nodeValue, c.table, node, $node)) {
                    return cur;
                }
            }
            // nothing found, return the generic parser (text)
            return ts.getParserById('text');
        },

        getElementText: function (c, node, cellIndex) {
            if (!node) { return ''; }
            var tmp,
                extract = c.textExtraction || '',
                // node could be a jquery object
                // http://jsperf.com/jquery-vs-instanceof-jquery/2
                $node = node.jquery ? node : $(node);
            if (typeof extract === 'string') {
                // check data-attribute first when set to 'basic'; don't use node.innerText - it's really slow!
                // http://www.kellegous.com/j/2013/02/27/innertext-vs-textcontent/
                if (extract === 'basic' && typeof (tmp = $node.attr(c.textAttribute)) !== 'undefined') {
                    return $.trim(tmp);
                }
                return $.trim(node.textContent || $node.text());
            } else {
                if (typeof extract === 'function') {
                    return $.trim(extract($node[0], c.table, cellIndex));
                } else if (typeof (tmp = ts.getColumnData(c.table, extract, cellIndex)) === 'function') {
                    return $.trim(tmp($node[0], c.table, cellIndex));
                }
            }
            // fallback
            return $.trim($node[0].textContent || $node.text());
        },

        // centralized function to extract/parse cell contents
        getParsedText: function (c, cell, colIndex, txt) {
            if (typeof txt === 'undefined') {
                txt = ts.getElementText(c, cell, colIndex);
            }
            // if no parser, make sure to return the txt
            var val = '' + txt,
                parser = c.parsers[colIndex],
                extractor = c.extractors[colIndex];
            if (parser) {
                // do extract before parsing, if there is one
                if (extractor && typeof extractor.format === 'function') {
                    txt = extractor.format(txt, c.table, cell, colIndex);
                }
                // allow parsing if the string is empty, previously parsing would change it to zero,
                // in case the parser needs to extract data from the table cell attributes
                val = parser.id === 'no-parser' ? '' :
                    // make sure txt is a string (extractor may have converted it)
                    parser.format('' + txt, c.table, cell, colIndex);
                if (c.ignoreCase && typeof val === 'string') {
                    val = val.toLowerCase();
                }
            }
            return val;
        },

		/*
		▄████▄ ▄████▄ ▄████▄ ██  ██ ██████
		██  ▀▀ ██▄▄██ ██  ▀▀ ██▄▄██ ██▄▄
		██  ▄▄ ██▀▀██ ██  ▄▄ ██▀▀██ ██▀▀
		▀████▀ ██  ██ ▀████▀ ██  ██ ██████
		*/
        buildCache: function (c, callback, $tbodies) {
            var cache, val, txt, rowIndex, colIndex, tbodyIndex, $tbody, $row,
                cols, $cells, cell, cacheTime, totalRows, rowData, prevRowData,
                colMax, span, cacheIndex, hasParser, max, len, index,
                table = c.table,
                parsers = c.parsers,
                debug = ts.debug(c, 'core');
            // update tbody variable
            c.$tbodies = c.$table.children('tbody:not(.' + c.cssInfoBlock + ')');
            $tbody = typeof $tbodies === 'undefined' ? c.$tbodies : $tbodies,
                c.cache = {};
            c.totalRows = 0;
            // if no parsers found, return - it's an empty table.
            if (!parsers) {
                return debug ? console.warn('Warning: *Empty table!* Not building a cache') : '';
            }
            if (debug) {
                cacheTime = new Date();
            }
            // processing icon
            if (c.showProcessing) {
                ts.isProcessing(table, true);
            }
            for (tbodyIndex = 0; tbodyIndex < $tbody.length; tbodyIndex++) {
                colMax = []; // column max value per tbody
                cache = c.cache[tbodyIndex] = {
                    normalized: [] // array of normalized row data; last entry contains 'rowData' above
                    // colMax: #   // added at the end
                };

                totalRows = ($tbody[tbodyIndex] && $tbody[tbodyIndex].rows.length) || 0;
                for (rowIndex = 0; rowIndex < totalRows; ++rowIndex) {
                    rowData = {
                        // order: original row order #
                        // $row : jQuery Object[]
                        child: [], // child row text (filter widget)
                        raw: []    // original row text
                    };
                    /** Add the table data to main data array */
                    $row = $($tbody[tbodyIndex].rows[rowIndex]);
                    cols = [];
                    // ignore "remove-me" rows
                    if ($row.hasClass(c.selectorRemove.slice(1))) {
                        continue;
                    }
                    // if this is a child row, add it to the last row's children and continue to the next row
                    // ignore child row class, if it is the first row
                    if ($row.hasClass(c.cssChildRow) && rowIndex !== 0) {
                        len = cache.normalized.length - 1;
                        prevRowData = cache.normalized[len][c.columns];
                        prevRowData.$row = prevRowData.$row.add($row);
                        // add 'hasChild' class name to parent row
                        if (!$row.prev().hasClass(c.cssChildRow)) {
                            $row.prev().addClass(ts.css.cssHasChild);
                        }
                        // save child row content (un-parsed!)
                        $cells = $row.children('th, td');
                        len = prevRowData.child.length;
                        prevRowData.child[len] = [];
                        // child row content does not account for colspans/rowspans; so indexing may be off
                        cacheIndex = 0;
                        max = c.columns;
                        for (colIndex = 0; colIndex < max; colIndex++) {
                            cell = $cells[colIndex];
                            if (cell) {
                                prevRowData.child[len][colIndex] = ts.getParsedText(c, cell, colIndex);
                                span = $cells[colIndex].colSpan - 1;
                                if (span > 0) {
                                    cacheIndex += span;
                                    max += span;
                                }
                            }
                            cacheIndex++;
                        }
                        // go to the next for loop
                        continue;
                    }
                    rowData.$row = $row;
                    rowData.order = rowIndex; // add original row position to rowCache
                    cacheIndex = 0;
                    max = c.columns;
                    for (colIndex = 0; colIndex < max; ++colIndex) {
                        cell = $row[0].cells[colIndex];
                        if (cell && cacheIndex < c.columns) {
                            hasParser = typeof parsers[cacheIndex] !== 'undefined';
                            if (!hasParser && debug) {
                                console.warn('No parser found for row: ' + rowIndex + ', column: ' + colIndex +
                                    '; cell containing: "' + $(cell).text() + '"; does it have a header?');
                            }
                            val = ts.getElementText(c, cell, cacheIndex);
                            rowData.raw[cacheIndex] = val; // save original row text
                            // save raw column text even if there is no parser set
                            txt = ts.getParsedText(c, cell, cacheIndex, val);
                            cols[cacheIndex] = txt;
                            if (hasParser && (parsers[cacheIndex].type || '').toLowerCase() === 'numeric') {
                                // determine column max value (ignore sign)
                                colMax[cacheIndex] = Math.max(Math.abs(txt) || 0, colMax[cacheIndex] || 0);
                            }
                            // allow colSpan in tbody
                            span = cell.colSpan - 1;
                            if (span > 0) {
                                index = 0;
                                while (index <= span) {
                                    // duplicate text (or not) to spanned columns
                                    // instead of setting duplicate span to empty string, use textExtraction to try to get a value
                                    // see http://stackoverflow.com/q/36449711/145346
                                    txt = c.duplicateSpan || index === 0 ?
                                        val :
                                        typeof c.textExtraction !== 'string' ?
                                            ts.getElementText(c, cell, cacheIndex + index) || '' :
                                            '';
                                    rowData.raw[cacheIndex + index] = txt;
                                    cols[cacheIndex + index] = txt;
                                    index++;
                                }
                                cacheIndex += span;
                                max += span;
                            }
                        }
                        cacheIndex++;
                    }
                    // ensure rowData is always in the same location (after the last column)
                    cols[c.columns] = rowData;
                    cache.normalized[cache.normalized.length] = cols;
                }
                cache.colMax = colMax;
                // total up rows, not including child rows
                c.totalRows += cache.normalized.length;

            }
            if (c.showProcessing) {
                ts.isProcessing(table); // remove processing icon
            }
            if (debug) {
                len = Math.min(5, c.cache[0].normalized.length);
                console[console.group ? 'group' : 'log']('Building cache for ' + c.totalRows +
                    ' rows (showing ' + len + ' rows in log) and ' + c.columns + ' columns' +
                    ts.benchmark(cacheTime));
                val = {};
                for (colIndex = 0; colIndex < c.columns; colIndex++) {
                    for (cacheIndex = 0; cacheIndex < len; cacheIndex++) {
                        if (!val['row: ' + cacheIndex]) {
                            val['row: ' + cacheIndex] = {};
                        }
                        val['row: ' + cacheIndex][c.$headerIndexed[colIndex].text()] =
                            c.cache[0].normalized[cacheIndex][colIndex];
                    }
                }
                console[console.table ? 'table' : 'log'](val);
                if (console.groupEnd) { console.groupEnd(); }
            }
            if ($.isFunction(callback)) {
                callback(table);
            }
        },

        getColumnText: function (table, column, callback, rowFilter) {
            table = $(table)[0];
            var tbodyIndex, rowIndex, cache, row, tbodyLen, rowLen, raw, parsed, $cell, result,
                hasCallback = typeof callback === 'function',
                allColumns = column === 'all',
                data = { raw: [], parsed: [], $cell: [] },
                c = table.config;
            if (ts.isEmptyObject(c)) {
                if (ts.debug(c, 'core')) {
                    console.warn('No cache found - aborting getColumnText function!');
                }
            } else {
                tbodyLen = c.$tbodies.length;
                for (tbodyIndex = 0; tbodyIndex < tbodyLen; tbodyIndex++) {
                    cache = c.cache[tbodyIndex].normalized;
                    rowLen = cache.length;
                    for (rowIndex = 0; rowIndex < rowLen; rowIndex++) {
                        row = cache[rowIndex];
                        if (rowFilter && !row[c.columns].$row.is(rowFilter)) {
                            continue;
                        }
                        result = true;
                        parsed = (allColumns) ? row.slice(0, c.columns) : row[column];
                        row = row[c.columns];
                        raw = (allColumns) ? row.raw : row.raw[column];
                        $cell = (allColumns) ? row.$row.children() : row.$row.children().eq(column);
                        if (hasCallback) {
                            result = callback({
                                tbodyIndex: tbodyIndex,
                                rowIndex: rowIndex,
                                parsed: parsed,
                                raw: raw,
                                $row: row.$row,
                                $cell: $cell
                            });
                        }
                        if (result !== false) {
                            data.parsed[data.parsed.length] = parsed;
                            data.raw[data.raw.length] = raw;
                            data.$cell[data.$cell.length] = $cell;
                        }
                    }
                }
                // return everything
                return data;
            }
        },

		/*
		██  ██ █████▄ █████▄ ▄████▄ ██████ ██████
		██  ██ ██▄▄██ ██  ██ ██▄▄██   ██   ██▄▄
		██  ██ ██▀▀▀  ██  ██ ██▀▀██   ██   ██▀▀
		▀████▀ ██     █████▀ ██  ██   ██   ██████
		*/
        setHeadersCss: function (c) {
            var indx, column,
                list = c.sortList,
                len = list.length,
                none = ts.css.sortNone + ' ' + c.cssNone,
                css = [ts.css.sortAsc + ' ' + c.cssAsc, ts.css.sortDesc + ' ' + c.cssDesc],
                cssIcon = [c.cssIconAsc, c.cssIconDesc, c.cssIconNone],
                aria = ['ascending', 'descending'],
                updateColumnSort = function ($el, index) {
                    $el
                        .removeClass(none)
                        .addClass(css[index])
                        .attr('aria-sort', aria[index])
                        .find('.' + ts.css.icon)
                        .removeClass(cssIcon[2])
                        .addClass(cssIcon[index]);
                },
                // find the footer
                $extras = c.$table
                    .find('tfoot tr')
                    .children('td, th')
                    .add($(c.namespace + '_extra_headers'))
                    .removeClass(css.join(' ')),
                // remove all header information
                $sorted = c.$headers
                    .add($('thead ' + c.namespace + '_extra_headers'))
                    .removeClass(css.join(' '))
                    .addClass(none)
                    .attr('aria-sort', 'none')
                    .find('.' + ts.css.icon)
                    .removeClass(cssIcon.join(' '))
                    .end();
            // add css none to all sortable headers
            $sorted
                .not('.sorter-false')
                .find('.' + ts.css.icon)
                .addClass(cssIcon[2]);
            // add disabled css icon class
            if (c.cssIconDisabled) {
                $sorted
                    .filter('.sorter-false')
                    .find('.' + ts.css.icon)
                    .addClass(c.cssIconDisabled);
            }
            for (indx = 0; indx < len; indx++) {
                // direction = 2 means reset!
                if (list[indx][1] !== 2) {
                    // multicolumn sorting updating - see #1005
                    // .not(function() {}) needs jQuery 1.4
                    // filter(function(i, el) {}) <- el is undefined in jQuery v1.2.6
                    $sorted = c.$headers.filter(function (i) {
                        // only include headers that are in the sortList (this includes colspans)
                        var include = true,
                            $el = c.$headers.eq(i),
                            col = parseInt($el.attr('data-column'), 10),
                            end = col + ts.getClosest($el, 'th, td')[0].colSpan;
                        for (; col < end; col++) {
                            include = include ? include || ts.isValueInArray(col, c.sortList) > -1 : false;
                        }
                        return include;
                    });

                    // choose the :last in case there are nested columns
                    $sorted = $sorted
                        .not('.sorter-false')
                        .filter('[data-column="' + list[indx][0] + '"]' + (len === 1 ? ':last' : ''));
                    if ($sorted.length) {
                        for (column = 0; column < $sorted.length; column++) {
                            if (!$sorted[column].sortDisabled) {
                                updateColumnSort($sorted.eq(column), list[indx][1]);
                            }
                        }
                    }
                    // add sorted class to footer & extra headers, if they exist
                    if ($extras.length) {
                        updateColumnSort($extras.filter('[data-column="' + list[indx][0] + '"]'), list[indx][1]);
                    }
                }
            }
            // add verbose aria labels
            len = c.$headers.length;
            for (indx = 0; indx < len; indx++) {
                ts.setColumnAriaLabel(c, c.$headers.eq(indx));
            }
        },

        getClosest: function ($el, selector) {
            // jQuery v1.2.6 doesn't have closest()
            if ($.fn.closest) {
                return $el.closest(selector);
            }
            return $el.is(selector) ?
                $el :
                $el.parents(selector).filter(':first');
        },

        // nextSort (optional), lets you disable next sort text
        setColumnAriaLabel: function (c, $header, nextSort) {
            if ($header.length) {
                var column = parseInt($header.attr('data-column'), 10),
                    vars = c.sortVars[column],
                    tmp = $header.hasClass(ts.css.sortAsc) ?
                        'sortAsc' :
                        $header.hasClass(ts.css.sortDesc) ? 'sortDesc' : 'sortNone',
                    txt = $.trim($header.text()) + ': ' + ts.language[tmp];
                if ($header.hasClass('sorter-false') || nextSort === false) {
                    txt += ts.language.sortDisabled;
                } else {
                    tmp = (vars.count + 1) % vars.order.length;
                    nextSort = vars.order[tmp];
                    // if nextSort
                    txt += ts.language[nextSort === 0 ? 'nextAsc' : nextSort === 1 ? 'nextDesc' : 'nextNone'];
                }
                $header.attr('aria-label', txt);
                if (vars.sortedBy) {
                    $header.attr('data-sortedBy', vars.sortedBy);
                } else {
                    $header.removeAttr('data-sortedBy');
                }
            }
        },

        updateHeader: function (c) {
            var index, isDisabled, $header, col,
                table = c.table,
                len = c.$headers.length;
            for (index = 0; index < len; index++) {
                $header = c.$headers.eq(index);
                col = ts.getColumnData(table, c.headers, index, true);
                // add 'sorter-false' class if 'parser-false' is set
                isDisabled = ts.getData($header, col, 'sorter') === 'false' || ts.getData($header, col, 'parser') === 'false';
                ts.setColumnSort(c, $header, isDisabled);
            }
        },

        setColumnSort: function (c, $header, isDisabled) {
            var id = c.table.id;
            $header[0].sortDisabled = isDisabled;
            $header[isDisabled ? 'addClass' : 'removeClass']('sorter-false')
                .attr('aria-disabled', '' + isDisabled);
            // disable tab index on disabled cells
            if (c.tabIndex) {
                if (isDisabled) {
                    $header.removeAttr('tabindex');
                } else {
                    $header.attr('tabindex', '0');
                }
            }
            // aria-controls - requires table ID
            if (id) {
                if (isDisabled) {
                    $header.removeAttr('aria-controls');
                } else {
                    $header.attr('aria-controls', id);
                }
            }
        },

        updateHeaderSortCount: function (c, list) {
            var col, dir, group, indx, primary, temp, val, order,
                sortList = list || c.sortList,
                len = sortList.length;
            c.sortList = [];
            for (indx = 0; indx < len; indx++) {
                val = sortList[indx];
                // ensure all sortList values are numeric - fixes #127
                col = parseInt(val[0], 10);
                // prevents error if sorton array is wrong
                if (col < c.columns) {

                    // set order if not already defined - due to colspan header without associated header cell
                    // adding this check prevents a javascript error
                    if (!c.sortVars[col].order) {
                        if (ts.getOrder(c.sortInitialOrder)) {
                            order = c.sortReset ? [1, 0, 2] : [1, 0];
                        } else {
                            order = c.sortReset ? [0, 1, 2] : [0, 1];
                        }
                        c.sortVars[col].order = order;
                        c.sortVars[col].count = 0;
                    }

                    order = c.sortVars[col].order;
                    dir = ('' + val[1]).match(/^(1|d|s|o|n)/);
                    dir = dir ? dir[0] : '';
                    // 0/(a)sc (default), 1/(d)esc, (s)ame, (o)pposite, (n)ext
                    switch (dir) {
                        case '1': case 'd': // descending
                            dir = 1;
                            break;
                        case 's': // same direction (as primary column)
                            // if primary sort is set to 's', make it ascending
                            dir = primary || 0;
                            break;
                        case 'o':
                            temp = order[(primary || 0) % order.length];
                            // opposite of primary column; but resets if primary resets
                            dir = temp === 0 ? 1 : temp === 1 ? 0 : 2;
                            break;
                        case 'n':
                            dir = order[(++c.sortVars[col].count) % order.length];
                            break;
                        default: // ascending
                            dir = 0;
                            break;
                    }
                    primary = indx === 0 ? dir : primary;
                    group = [col, parseInt(dir, 10) || 0];
                    c.sortList[c.sortList.length] = group;
                    dir = $.inArray(group[1], order); // fixes issue #167
                    c.sortVars[col].count = dir >= 0 ? dir : group[1] % order.length;
                }
            }
        },

        updateAll: function (c, resort, callback) {
            var table = c.table;
            table.isUpdating = true;
            ts.refreshWidgets(table, true, true);
            ts.buildHeaders(c);
            ts.bindEvents(table, c.$headers, true);
            ts.bindMethods(c);
            ts.commonUpdate(c, resort, callback);
        },

        update: function (c, resort, callback) {
            var table = c.table;
            table.isUpdating = true;
            // update sorting (if enabled/disabled)
            ts.updateHeader(c);
            ts.commonUpdate(c, resort, callback);
        },

        // simple header update - see #989
        updateHeaders: function (c, callback) {
            c.table.isUpdating = true;
            ts.buildHeaders(c);
            ts.bindEvents(c.table, c.$headers, true);
            ts.resortComplete(c, callback);
        },

        updateCell: function (c, cell, resort, callback) {
            // updateCell for child rows is a mess - we'll ignore them for now
            // eventually I'll break out the "update" row cache code to make everything consistent
            if ($(cell).closest('tr').hasClass(c.cssChildRow)) {
                console.warn('Tablesorter Warning! "updateCell" for child row content has been disabled, use "update" instead');
                return;
            }
            if (ts.isEmptyObject(c.cache)) {
                // empty table, do an update instead - fixes #1099
                ts.updateHeader(c);
                ts.commonUpdate(c, resort, callback);
                return;
            }
            c.table.isUpdating = true;
            c.$table.find(c.selectorRemove).remove();
            // get position from the dom
            var tmp, indx, row, icell, cache, len,
                $tbodies = c.$tbodies,
                $cell = $(cell),
                // update cache - format: function( s, table, cell, cellIndex )
                // no closest in jQuery v1.2.6
                tbodyIndex = $tbodies.index(ts.getClosest($cell, 'tbody')),
                tbcache = c.cache[tbodyIndex],
                $row = ts.getClosest($cell, 'tr');
            cell = $cell[0]; // in case cell is a jQuery object
            // tbody may not exist if update is initialized while tbody is removed for processing
            if ($tbodies.length && tbodyIndex >= 0) {
                row = $tbodies.eq(tbodyIndex).find('tr').not('.' + c.cssChildRow).index($row);
                cache = tbcache.normalized[row];
                len = $row[0].cells.length;
                if (len !== c.columns) {
                    // colspan in here somewhere!
                    icell = 0;
                    tmp = false;
                    for (indx = 0; indx < len; indx++) {
                        if (!tmp && $row[0].cells[indx] !== cell) {
                            icell += $row[0].cells[indx].colSpan;
                        } else {
                            tmp = true;
                        }
                    }
                } else {
                    icell = $cell.index();
                }
                tmp = ts.getElementText(c, cell, icell); // raw
                cache[c.columns].raw[icell] = tmp;
                tmp = ts.getParsedText(c, cell, icell, tmp);
                cache[icell] = tmp; // parsed
                if ((c.parsers[icell].type || '').toLowerCase() === 'numeric') {
                    // update column max value (ignore sign)
                    tbcache.colMax[icell] = Math.max(Math.abs(tmp) || 0, tbcache.colMax[icell] || 0);
                }
                tmp = resort !== 'undefined' ? resort : c.resort;
                if (tmp !== false) {
                    // widgets will be reapplied
                    ts.checkResort(c, tmp, callback);
                } else {
                    // don't reapply widgets is resort is false, just in case it causes
                    // problems with element focus
                    ts.resortComplete(c, callback);
                }
            } else {
                if (ts.debug(c, 'core')) {
                    console.error('updateCell aborted, tbody missing or not within the indicated table');
                }
                c.table.isUpdating = false;
            }
        },

        addRows: function (c, $row, resort, callback) {
            var txt, val, tbodyIndex, rowIndex, rows, cellIndex, len, order,
                cacheIndex, rowData, cells, cell, span,
                // allow passing a row string if only one non-info tbody exists in the table
                valid = typeof $row === 'string' && c.$tbodies.length === 1 && /<tr/.test($row || ''),
                table = c.table;
            if (valid) {
                $row = $($row);
                c.$tbodies.append($row);
            } else if (
                !$row ||
                // row is a jQuery object?
                !($row instanceof $) ||
                // row contained in the table?
                (ts.getClosest($row, 'table')[0] !== c.table)
            ) {
                if (ts.debug(c, 'core')) {
                    console.error('addRows method requires (1) a jQuery selector reference to rows that have already ' +
                        'been added to the table, or (2) row HTML string to be added to a table with only one tbody');
                }
                return false;
            }
            table.isUpdating = true;
            if (ts.isEmptyObject(c.cache)) {
                // empty table, do an update instead - fixes #450
                ts.updateHeader(c);
                ts.commonUpdate(c, resort, callback);
            } else {
                rows = $row.filter('tr').attr('role', 'row').length;
                tbodyIndex = c.$tbodies.index($row.parents('tbody').filter(':first'));
                // fixes adding rows to an empty table - see issue #179
                if (!(c.parsers && c.parsers.length)) {
                    ts.setupParsers(c);
                }
                // add each row
                for (rowIndex = 0; rowIndex < rows; rowIndex++) {
                    cacheIndex = 0;
                    len = $row[rowIndex].cells.length;
                    order = c.cache[tbodyIndex].normalized.length;
                    cells = [];
                    rowData = {
                        child: [],
                        raw: [],
                        $row: $row.eq(rowIndex),
                        order: order
                    };
                    // add each cell
                    for (cellIndex = 0; cellIndex < len; cellIndex++) {
                        cell = $row[rowIndex].cells[cellIndex];
                        txt = ts.getElementText(c, cell, cacheIndex);
                        rowData.raw[cacheIndex] = txt;
                        val = ts.getParsedText(c, cell, cacheIndex, txt);
                        cells[cacheIndex] = val;
                        if ((c.parsers[cacheIndex].type || '').toLowerCase() === 'numeric') {
                            // update column max value (ignore sign)
                            c.cache[tbodyIndex].colMax[cacheIndex] =
                                Math.max(Math.abs(val) || 0, c.cache[tbodyIndex].colMax[cacheIndex] || 0);
                        }
                        span = cell.colSpan - 1;
                        if (span > 0) {
                            cacheIndex += span;
                        }
                        cacheIndex++;
                    }
                    // add the row data to the end
                    cells[c.columns] = rowData;
                    // update cache
                    c.cache[tbodyIndex].normalized[order] = cells;
                }
                // resort using current settings
                ts.checkResort(c, resort, callback);
            }
        },

        updateCache: function (c, callback, $tbodies) {
            // rebuild parsers
            if (!(c.parsers && c.parsers.length)) {
                ts.setupParsers(c, $tbodies);
            }
            // rebuild the cache map
            ts.buildCache(c, callback, $tbodies);
        },

        // init flag (true) used by pager plugin to prevent widget application
        // renamed from appendToTable
        appendCache: function (c, init) {
            var parsed, totalRows, $tbody, $curTbody, rowIndex, tbodyIndex, appendTime,
                table = c.table,
                $tbodies = c.$tbodies,
                rows = [],
                cache = c.cache;
            // empty table - fixes #206/#346
            if (ts.isEmptyObject(cache)) {
                // run pager appender in case the table was just emptied
                return c.appender ? c.appender(table, rows) :
                    table.isUpdating ? c.$table.triggerHandler('updateComplete', table) : ''; // Fixes #532
            }
            if (ts.debug(c, 'core')) {
                appendTime = new Date();
            }
            for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++) {
                $tbody = $tbodies.eq(tbodyIndex);
                if ($tbody.length) {
                    // detach tbody for manipulation
                    $curTbody = ts.processTbody(table, $tbody, true);
                    parsed = cache[tbodyIndex].normalized;
                    totalRows = parsed.length;
                    for (rowIndex = 0; rowIndex < totalRows; rowIndex++) {
                        rows[rows.length] = parsed[rowIndex][c.columns].$row;
                        // removeRows used by the pager plugin; don't render if using ajax - fixes #411
                        if (!c.appender || (c.pager && !c.pager.removeRows && !c.pager.ajax)) {
                            $curTbody.append(parsed[rowIndex][c.columns].$row);
                        }
                    }
                    // restore tbody
                    ts.processTbody(table, $curTbody, false);
                }
            }
            if (c.appender) {
                c.appender(table, rows);
            }
            if (ts.debug(c, 'core')) {
                console.log('Rebuilt table' + ts.benchmark(appendTime));
            }
            // apply table widgets; but not before ajax completes
            if (!init && !c.appender) {
                ts.applyWidget(table);
            }
            if (table.isUpdating) {
                c.$table.triggerHandler('updateComplete', table);
            }
        },

        commonUpdate: function (c, resort, callback) {
            // remove rows/elements before update
            c.$table.find(c.selectorRemove).remove();
            // rebuild parsers
            ts.setupParsers(c);
            // rebuild the cache map
            ts.buildCache(c);
            ts.checkResort(c, resort, callback);
        },

		/*
		▄█████ ▄████▄ █████▄ ██████ ██ █████▄ ▄████▄
		▀█▄    ██  ██ ██▄▄██   ██   ██ ██  ██ ██ ▄▄▄
		   ▀█▄ ██  ██ ██▀██    ██   ██ ██  ██ ██ ▀██
		█████▀ ▀████▀ ██  ██   ██   ██ ██  ██ ▀████▀
		*/
        initSort: function (c, cell, event) {
            if (c.table.isUpdating) {
                // let any updates complete before initializing a sort
                return setTimeout(function () {
                    ts.initSort(c, cell, event);
                }, 50);
            }

            var arry, indx, headerIndx, dir, temp, tmp, $header,
                notMultiSort = !event[c.sortMultiSortKey],
                table = c.table,
                len = c.$headers.length,
                th = ts.getClosest($(cell), 'th, td'),
                col = parseInt(th.attr('data-column'), 10),
                sortedBy = event.type === 'mouseup' ? 'user' : event.type,
                order = c.sortVars[col].order;
            th = th[0];
            // Only call sortStart if sorting is enabled
            c.$table.triggerHandler('sortStart', table);
            // get current column sort order
            tmp = (c.sortVars[col].count + 1) % order.length;
            c.sortVars[col].count = event[c.sortResetKey] ? 2 : tmp;
            // reset all sorts on non-current column - issue #30
            if (c.sortRestart) {
                for (headerIndx = 0; headerIndx < len; headerIndx++) {
                    $header = c.$headers.eq(headerIndx);
                    tmp = parseInt($header.attr('data-column'), 10);
                    // only reset counts on columns that weren't just clicked on and if not included in a multisort
                    if (col !== tmp && (notMultiSort || $header.hasClass(ts.css.sortNone))) {
                        c.sortVars[tmp].count = -1;
                    }
                }
            }
            // user only wants to sort on one column
            if (notMultiSort) {
                $.each(c.sortVars, function (i) {
                    c.sortVars[i].sortedBy = '';
                });
                // flush the sort list
                c.sortList = [];
                c.last.sortList = [];
                if (c.sortForce !== null) {
                    arry = c.sortForce;
                    for (indx = 0; indx < arry.length; indx++) {
                        if (arry[indx][0] !== col) {
                            c.sortList[c.sortList.length] = arry[indx];
                            c.sortVars[arry[indx][0]].sortedBy = 'sortForce';
                        }
                    }
                }
                // add column to sort list
                dir = order[c.sortVars[col].count];
                if (dir < 2) {
                    c.sortList[c.sortList.length] = [col, dir];
                    c.sortVars[col].sortedBy = sortedBy;
                    // add other columns if header spans across multiple
                    if (th.colSpan > 1) {
                        for (indx = 1; indx < th.colSpan; indx++) {
                            c.sortList[c.sortList.length] = [col + indx, dir];
                            // update count on columns in colSpan
                            c.sortVars[col + indx].count = $.inArray(dir, order);
                            c.sortVars[col + indx].sortedBy = sortedBy;
                        }
                    }
                }
                // multi column sorting
            } else {
                // get rid of the sortAppend before adding more - fixes issue #115 & #523
                c.sortList = $.extend([], c.last.sortList);

                // the user has clicked on an already sorted column
                if (ts.isValueInArray(col, c.sortList) >= 0) {
                    // reverse the sorting direction
                    c.sortVars[col].sortedBy = sortedBy;
                    for (indx = 0; indx < c.sortList.length; indx++) {
                        tmp = c.sortList[indx];
                        if (tmp[0] === col) {
                            // order.count seems to be incorrect when compared to cell.count
                            tmp[1] = order[c.sortVars[col].count];
                            if (tmp[1] === 2) {
                                c.sortList.splice(indx, 1);
                                c.sortVars[col].count = -1;
                            }
                        }
                    }
                } else {
                    // add column to sort list array
                    dir = order[c.sortVars[col].count];
                    c.sortVars[col].sortedBy = sortedBy;
                    if (dir < 2) {
                        c.sortList[c.sortList.length] = [col, dir];
                        // add other columns if header spans across multiple
                        if (th.colSpan > 1) {
                            for (indx = 1; indx < th.colSpan; indx++) {
                                c.sortList[c.sortList.length] = [col + indx, dir];
                                // update count on columns in colSpan
                                c.sortVars[col + indx].count = $.inArray(dir, order);
                                c.sortVars[col + indx].sortedBy = sortedBy;
                            }
                        }
                    }
                }
            }
            // save sort before applying sortAppend
            c.last.sortList = $.extend([], c.sortList);
            if (c.sortList.length && c.sortAppend) {
                arry = $.isArray(c.sortAppend) ? c.sortAppend : c.sortAppend[c.sortList[0][0]];
                if (!ts.isEmptyObject(arry)) {
                    for (indx = 0; indx < arry.length; indx++) {
                        if (arry[indx][0] !== col && ts.isValueInArray(arry[indx][0], c.sortList) < 0) {
                            dir = arry[indx][1];
                            temp = ('' + dir).match(/^(a|d|s|o|n)/);
                            if (temp) {
                                tmp = c.sortList[0][1];
                                switch (temp[0]) {
                                    case 'd':
                                        dir = 1;
                                        break;
                                    case 's':
                                        dir = tmp;
                                        break;
                                    case 'o':
                                        dir = tmp === 0 ? 1 : 0;
                                        break;
                                    case 'n':
                                        dir = (tmp + 1) % order.length;
                                        break;
                                    default:
                                        dir = 0;
                                        break;
                                }
                            }
                            c.sortList[c.sortList.length] = [arry[indx][0], dir];
                            c.sortVars[arry[indx][0]].sortedBy = 'sortAppend';
                        }
                    }
                }
            }
            // sortBegin event triggered immediately before the sort
            c.$table.triggerHandler('sortBegin', table);
            // setTimeout needed so the processing icon shows up
            setTimeout(function () {
                // set css for headers
                ts.setHeadersCss(c);
                ts.multisort(c);
                ts.appendCache(c);
                c.$table.triggerHandler('sortBeforeEnd', table);
                c.$table.triggerHandler('sortEnd', table);
            }, 1);
        },

        // sort multiple columns
        multisort: function (c) { /*jshint loopfunc:true */
            var tbodyIndex, sortTime, colMax, rows, tmp,
                table = c.table,
                sorter = [],
                dir = 0,
                textSorter = c.textSorter || '',
                sortList = c.sortList,
                sortLen = sortList.length,
                len = c.$tbodies.length;
            if (c.serverSideSorting || ts.isEmptyObject(c.cache)) {
                // empty table - fixes #206/#346
                return;
            }
            if (ts.debug(c, 'core')) { sortTime = new Date(); }
            // cache textSorter to optimize speed
            if (typeof textSorter === 'object') {
                colMax = c.columns;
                while (colMax--) {
                    tmp = ts.getColumnData(table, textSorter, colMax);
                    if (typeof tmp === 'function') {
                        sorter[colMax] = tmp;
                    }
                }
            }
            for (tbodyIndex = 0; tbodyIndex < len; tbodyIndex++) {
                colMax = c.cache[tbodyIndex].colMax;
                rows = c.cache[tbodyIndex].normalized;

                rows.sort(function (a, b) {
                    var sortIndex, num, col, order, sort, x, y;
                    // rows is undefined here in IE, so don't use it!
                    for (sortIndex = 0; sortIndex < sortLen; sortIndex++) {
                        col = sortList[sortIndex][0];
                        order = sortList[sortIndex][1];
                        // sort direction, true = asc, false = desc
                        dir = order === 0;

                        if (c.sortStable && a[col] === b[col] && sortLen === 1) {
                            return a[c.columns].order - b[c.columns].order;
                        }

                        // fallback to natural sort since it is more robust
                        num = /n/i.test(ts.getSortType(c.parsers, col));
                        if (num && c.strings[col]) {
                            // sort strings in numerical columns
                            if (typeof (ts.string[c.strings[col]]) === 'boolean') {
                                num = (dir ? 1 : -1) * (ts.string[c.strings[col]] ? -1 : 1);
                            } else {
                                num = (c.strings[col]) ? ts.string[c.strings[col]] || 0 : 0;
                            }
                            // fall back to built-in numeric sort
                            // var sort = $.tablesorter['sort' + s]( a[col], b[col], dir, colMax[col], table );
                            sort = c.numberSorter ? c.numberSorter(a[col], b[col], dir, colMax[col], table) :
                                ts['sortNumeric' + (dir ? 'Asc' : 'Desc')](a[col], b[col], num, colMax[col], col, c);
                        } else {
                            // set a & b depending on sort direction
                            x = dir ? a : b;
                            y = dir ? b : a;
                            // text sort function
                            if (typeof textSorter === 'function') {
                                // custom OVERALL text sorter
                                sort = textSorter(x[col], y[col], dir, col, table);
                            } else if (typeof sorter[col] === 'function') {
                                // custom text sorter for a SPECIFIC COLUMN
                                sort = sorter[col](x[col], y[col], dir, col, table);
                            } else {
                                // fall back to natural sort
                                sort = ts['sortNatural' + (dir ? 'Asc' : 'Desc')](a[col] || '', b[col] || '', col, c);
                            }
                        }
                        if (sort) { return sort; }
                    }
                    return a[c.columns].order - b[c.columns].order;
                });
            }
            if (ts.debug(c, 'core')) {
                console.log('Applying sort ' + sortList.toString() + ts.benchmark(sortTime));
            }
        },

        resortComplete: function (c, callback) {
            if (c.table.isUpdating) {
                c.$table.triggerHandler('updateComplete', c.table);
            }
            if ($.isFunction(callback)) {
                callback(c.table);
            }
        },

        checkResort: function (c, resort, callback) {
            var sortList = $.isArray(resort) ? resort : c.sortList,
                // if no resort parameter is passed, fallback to config.resort (true by default)
                resrt = typeof resort === 'undefined' ? c.resort : resort;
            // don't try to resort if the table is still processing
            // this will catch spamming of the updateCell method
            if (resrt !== false && !c.serverSideSorting && !c.table.isProcessing) {
                if (sortList.length) {
                    ts.sortOn(c, sortList, function () {
                        ts.resortComplete(c, callback);
                    }, true);
                } else {
                    ts.sortReset(c, function () {
                        ts.resortComplete(c, callback);
                        ts.applyWidget(c.table, false);
                    });
                }
            } else {
                ts.resortComplete(c, callback);
                ts.applyWidget(c.table, false);
            }
        },

        sortOn: function (c, list, callback, init) {
            var indx,
                table = c.table;
            c.$table.triggerHandler('sortStart', table);
            for (indx = 0; indx < c.columns; indx++) {
                c.sortVars[indx].sortedBy = ts.isValueInArray(indx, list) > -1 ? 'sorton' : '';
            }
            // update header count index
            ts.updateHeaderSortCount(c, list);
            // set css for headers
            ts.setHeadersCss(c);
            // fixes #346
            if (c.delayInit && ts.isEmptyObject(c.cache)) {
                ts.buildCache(c);
            }
            c.$table.triggerHandler('sortBegin', table);
            // sort the table and append it to the dom
            ts.multisort(c);
            ts.appendCache(c, init);
            c.$table.triggerHandler('sortBeforeEnd', table);
            c.$table.triggerHandler('sortEnd', table);
            ts.applyWidget(table);
            if ($.isFunction(callback)) {
                callback(table);
            }
        },

        sortReset: function (c, callback) {
            c.sortList = [];
            var indx;
            for (indx = 0; indx < c.columns; indx++) {
                c.sortVars[indx].count = -1;
                c.sortVars[indx].sortedBy = '';
            }
            ts.setHeadersCss(c);
            ts.multisort(c);
            ts.appendCache(c);
            if ($.isFunction(callback)) {
                callback(c.table);
            }
        },

        getSortType: function (parsers, column) {
            return (parsers && parsers[column]) ? parsers[column].type || '' : '';
        },

        getOrder: function (val) {
            // look for 'd' in 'desc' order; return true
            return (/^d/i.test(val) || val === 1);
        },

        // Natural sort - https://github.com/overset/javascript-natural-sort (date sorting removed)
        sortNatural: function (a, b) {
            if (a === b) { return 0; }
            a = (a || '').toString();
            b = (b || '').toString();
            var aNum, bNum, aFloat, bFloat, indx, max,
                regex = ts.regex;
            // first try and sort Hex codes
            if (regex.hex.test(b)) {
                aNum = parseInt(a.match(regex.hex), 16);
                bNum = parseInt(b.match(regex.hex), 16);
                if (aNum < bNum) { return -1; }
                if (aNum > bNum) { return 1; }
            }
            // chunk/tokenize
            aNum = a.replace(regex.chunk, '\\0$1\\0').replace(regex.chunks, '').split('\\0');
            bNum = b.replace(regex.chunk, '\\0$1\\0').replace(regex.chunks, '').split('\\0');
            max = Math.max(aNum.length, bNum.length);
            // natural sorting through split numeric strings and default strings
            for (indx = 0; indx < max; indx++) {
                // find floats not starting with '0', string or 0 if not defined
                aFloat = isNaN(aNum[indx]) ? aNum[indx] || 0 : parseFloat(aNum[indx]) || 0;
                bFloat = isNaN(bNum[indx]) ? bNum[indx] || 0 : parseFloat(bNum[indx]) || 0;
                // handle numeric vs string comparison - number < string - (Kyle Adams)
                if (isNaN(aFloat) !== isNaN(bFloat)) { return isNaN(aFloat) ? 1 : -1; }
                // rely on string comparison if different types - i.e. '02' < 2 != '02' < '2'
                if (typeof aFloat !== typeof bFloat) {
                    aFloat += '';
                    bFloat += '';
                }
                if (aFloat < bFloat) { return -1; }
                if (aFloat > bFloat) { return 1; }
            }
            return 0;
        },

        sortNaturalAsc: function (a, b, col, c) {
            if (a === b) { return 0; }
            var empty = ts.string[(c.empties[col] || c.emptyTo)];
            if (a === '' && empty !== 0) { return typeof empty === 'boolean' ? (empty ? -1 : 1) : -empty || -1; }
            if (b === '' && empty !== 0) { return typeof empty === 'boolean' ? (empty ? 1 : -1) : empty || 1; }
            return ts.sortNatural(a, b);
        },

        sortNaturalDesc: function (a, b, col, c) {
            if (a === b) { return 0; }
            var empty = ts.string[(c.empties[col] || c.emptyTo)];
            if (a === '' && empty !== 0) { return typeof empty === 'boolean' ? (empty ? -1 : 1) : empty || 1; }
            if (b === '' && empty !== 0) { return typeof empty === 'boolean' ? (empty ? 1 : -1) : -empty || -1; }
            return ts.sortNatural(b, a);
        },

        // basic alphabetical sort
        sortText: function (a, b) {
            return a > b ? 1 : (a < b ? -1 : 0);
        },

        // return text string value by adding up ascii value
        // so the text is somewhat sorted when using a digital sort
        // this is NOT an alphanumeric sort
        getTextValue: function (val, num, max) {
            if (max) {
                // make sure the text value is greater than the max numerical value (max)
                var indx,
                    len = val ? val.length : 0,
                    n = max + num;
                for (indx = 0; indx < len; indx++) {
                    n += val.charCodeAt(indx);
                }
                return num * n;
            }
            return 0;
        },

        sortNumericAsc: function (a, b, num, max, col, c) {
            if (a === b) { return 0; }
            var empty = ts.string[(c.empties[col] || c.emptyTo)];
            if (a === '' && empty !== 0) { return typeof empty === 'boolean' ? (empty ? -1 : 1) : -empty || -1; }
            if (b === '' && empty !== 0) { return typeof empty === 'boolean' ? (empty ? 1 : -1) : empty || 1; }
            if (isNaN(a)) { a = ts.getTextValue(a, num, max); }
            if (isNaN(b)) { b = ts.getTextValue(b, num, max); }
            return a - b;
        },

        sortNumericDesc: function (a, b, num, max, col, c) {
            if (a === b) { return 0; }
            var empty = ts.string[(c.empties[col] || c.emptyTo)];
            if (a === '' && empty !== 0) { return typeof empty === 'boolean' ? (empty ? -1 : 1) : empty || 1; }
            if (b === '' && empty !== 0) { return typeof empty === 'boolean' ? (empty ? 1 : -1) : -empty || -1; }
            if (isNaN(a)) { a = ts.getTextValue(a, num, max); }
            if (isNaN(b)) { b = ts.getTextValue(b, num, max); }
            return b - a;
        },

        sortNumeric: function (a, b) {
            return a - b;
        },

		/*
		██ ██ ██ ██ █████▄ ▄████▄ ██████ ██████ ▄█████
		██ ██ ██ ██ ██  ██ ██ ▄▄▄ ██▄▄     ██   ▀█▄
		██ ██ ██ ██ ██  ██ ██ ▀██ ██▀▀     ██      ▀█▄
		███████▀ ██ █████▀ ▀████▀ ██████   ██   █████▀
		*/
        addWidget: function (widget) {
            if (widget.id && !ts.isEmptyObject(ts.getWidgetById(widget.id))) {
                console.warn('"' + widget.id + '" widget was loaded more than once!');
            }
            ts.widgets[ts.widgets.length] = widget;
        },

        hasWidget: function ($table, name) {
            $table = $($table);
            return $table.length && $table[0].config && $table[0].config.widgetInit[name] || false;
        },

        getWidgetById: function (name) {
            var indx, widget,
                len = ts.widgets.length;
            for (indx = 0; indx < len; indx++) {
                widget = ts.widgets[indx];
                if (widget && widget.id && widget.id.toLowerCase() === name.toLowerCase()) {
                    return widget;
                }
            }
        },

        applyWidgetOptions: function (table) {
            var indx, widget, wo,
                c = table.config,
                len = c.widgets.length;
            if (len) {
                for (indx = 0; indx < len; indx++) {
                    widget = ts.getWidgetById(c.widgets[indx]);
                    if (widget && widget.options) {
                        wo = $.extend(true, {}, widget.options);
                        c.widgetOptions = $.extend(true, wo, c.widgetOptions);
                        // add widgetOptions to defaults for option validator
                        $.extend(true, ts.defaults.widgetOptions, widget.options);
                    }
                }
            }
        },

        addWidgetFromClass: function (table) {
            var len, indx,
                c = table.config,
                // look for widgets to apply from table class
                // don't match from 'ui-widget-content'; use \S instead of \w to include widgets
                // with dashes in the name, e.g. "widget-test-2" extracts out "test-2"
                regex = '^' + c.widgetClass.replace(ts.regex.templateName, '(\\S+)+') + '$',
                widgetClass = new RegExp(regex, 'g'),
                // split up table class (widget id's can include dashes) - stop using match
                // otherwise only one widget gets extracted, see #1109
                widgets = (table.className || '').split(ts.regex.spaces);
            if (widgets.length) {
                len = widgets.length;
                for (indx = 0; indx < len; indx++) {
                    if (widgets[indx].match(widgetClass)) {
                        c.widgets[c.widgets.length] = widgets[indx].replace(widgetClass, '$1');
                    }
                }
            }
        },

        applyWidgetId: function (table, id, init) {
            table = $(table)[0];
            var applied, time, name,
                c = table.config,
                wo = c.widgetOptions,
                debug = ts.debug(c, 'core'),
                widget = ts.getWidgetById(id);
            if (widget) {
                name = widget.id;
                applied = false;
                // add widget name to option list so it gets reapplied after sorting, filtering, etc
                if ($.inArray(name, c.widgets) < 0) {
                    c.widgets[c.widgets.length] = name;
                }
                if (debug) { time = new Date(); }

                if (init || !(c.widgetInit[name])) {
                    // set init flag first to prevent calling init more than once (e.g. pager)
                    c.widgetInit[name] = true;
                    if (table.hasInitialized) {
                        // don't reapply widget options on tablesorter init
                        ts.applyWidgetOptions(table);
                    }
                    if (typeof widget.init === 'function') {
                        applied = true;
                        if (debug) {
                            console[console.group ? 'group' : 'log']('Initializing ' + name + ' widget');
                        }
                        widget.init(table, widget, c, wo);
                    }
                }
                if (!init && typeof widget.format === 'function') {
                    applied = true;
                    if (debug) {
                        console[console.group ? 'group' : 'log']('Updating ' + name + ' widget');
                    }
                    widget.format(table, c, wo, false);
                }
                if (debug) {
                    if (applied) {
                        console.log('Completed ' + (init ? 'initializing ' : 'applying ') + name + ' widget' + ts.benchmark(time));
                        if (console.groupEnd) { console.groupEnd(); }
                    }
                }
            }
        },

        applyWidget: function (table, init, callback) {
            table = $(table)[0]; // in case this is called externally
            var indx, len, names, widget, time,
                c = table.config,
                debug = ts.debug(c, 'core'),
                widgets = [];
            // prevent numerous consecutive widget applications
            if (init !== false && table.hasInitialized && (table.isApplyingWidgets || table.isUpdating)) {
                return;
            }
            if (debug) { time = new Date(); }
            ts.addWidgetFromClass(table);
            // prevent "tablesorter-ready" from firing multiple times in a row
            clearTimeout(c.timerReady);
            if (c.widgets.length) {
                table.isApplyingWidgets = true;
                // ensure unique widget ids
                c.widgets = $.grep(c.widgets, function (val, index) {
                    return $.inArray(val, c.widgets) === index;
                });
                names = c.widgets || [];
                len = names.length;
                // build widget array & add priority as needed
                for (indx = 0; indx < len; indx++) {
                    widget = ts.getWidgetById(names[indx]);
                    if (widget && widget.id) {
                        // set priority to 10 if not defined
                        if (!widget.priority) { widget.priority = 10; }
                        widgets[indx] = widget;
                    } else if (debug) {
                        console.warn('"' + names[indx] + '" was enabled, but the widget code has not been loaded!');
                    }
                }
                // sort widgets by priority
                widgets.sort(function (a, b) {
                    return a.priority < b.priority ? -1 : a.priority === b.priority ? 0 : 1;
                });
                // add/update selected widgets
                len = widgets.length;
                if (debug) {
                    console[console.group ? 'group' : 'log']('Start ' + (init ? 'initializing' : 'applying') + ' widgets');
                }
                for (indx = 0; indx < len; indx++) {
                    widget = widgets[indx];
                    if (widget && widget.id) {
                        ts.applyWidgetId(table, widget.id, init);
                    }
                }
                if (debug && console.groupEnd) { console.groupEnd(); }
            }
            c.timerReady = setTimeout(function () {
                table.isApplyingWidgets = false;
                $.data(table, 'lastWidgetApplication', new Date());
                c.$table.triggerHandler('tablesorter-ready');
                // callback executed on init only
                if (!init && typeof callback === 'function') {
                    callback(table);
                }
                if (debug) {
                    widget = c.widgets.length;
                    console.log('Completed ' +
                        (init === true ? 'initializing ' : 'applying ') + widget +
                        ' widget' + (widget !== 1 ? 's' : '') + ts.benchmark(time));
                }
            }, 10);
        },

        removeWidget: function (table, name, refreshing) {
            table = $(table)[0];
            var index, widget, indx, len,
                c = table.config;
            // if name === true, add all widgets from $.tablesorter.widgets
            if (name === true) {
                name = [];
                len = ts.widgets.length;
                for (indx = 0; indx < len; indx++) {
                    widget = ts.widgets[indx];
                    if (widget && widget.id) {
                        name[name.length] = widget.id;
                    }
                }
            } else {
                // name can be either an array of widgets names,
                // or a space/comma separated list of widget names
                name = ($.isArray(name) ? name.join(',') : name || '').toLowerCase().split(/[\s,]+/);
            }
            len = name.length;
            for (index = 0; index < len; index++) {
                widget = ts.getWidgetById(name[index]);
                indx = $.inArray(name[index], c.widgets);
                // don't remove the widget from config.widget if refreshing
                if (indx >= 0 && refreshing !== true) {
                    c.widgets.splice(indx, 1);
                }
                if (widget && widget.remove) {
                    if (ts.debug(c, 'core')) {
                        console.log((refreshing ? 'Refreshing' : 'Removing') + ' "' + name[index] + '" widget');
                    }
                    widget.remove(table, c, c.widgetOptions, refreshing);
                    c.widgetInit[name[index]] = false;
                }
            }
            c.$table.triggerHandler('widgetRemoveEnd', table);
        },

        refreshWidgets: function (table, doAll, dontapply) {
            table = $(table)[0]; // see issue #243
            var indx, widget,
                c = table.config,
                curWidgets = c.widgets,
                widgets = ts.widgets,
                len = widgets.length,
                list = [],
                callback = function (table) {
                    $(table).triggerHandler('refreshComplete');
                };
            // remove widgets not defined in config.widgets, unless doAll is true
            for (indx = 0; indx < len; indx++) {
                widget = widgets[indx];
                if (widget && widget.id && (doAll || $.inArray(widget.id, curWidgets) < 0)) {
                    list[list.length] = widget.id;
                }
            }
            ts.removeWidget(table, list.join(','), true);
            if (dontapply !== true) {
                // call widget init if
                ts.applyWidget(table, doAll || false, callback);
                if (doAll) {
                    // apply widget format
                    ts.applyWidget(table, false, callback);
                }
            } else {
                callback(table);
            }
        },

		/*
		██  ██ ██████ ██ ██     ██ ██████ ██ ██████ ▄█████
		██  ██   ██   ██ ██     ██   ██   ██ ██▄▄   ▀█▄
		██  ██   ██   ██ ██     ██   ██   ██ ██▀▀      ▀█▄
		▀████▀   ██   ██ ██████ ██   ██   ██ ██████ █████▀
		*/
        benchmark: function (diff) {
            return (' (' + (new Date().getTime() - diff.getTime()) + ' ms)');
        },
        // deprecated ts.log
        log: function () {
            console.log(arguments);
        },
        debug: function (c, name) {
            return c && (
                c.debug === true ||
                typeof c.debug === 'string' && c.debug.indexOf(name) > -1
            );
        },

        // $.isEmptyObject from jQuery v1.4
        isEmptyObject: function (obj) {
            /*jshint forin: false */
            for (var name in obj) {
                return false;
            }
            return true;
        },

        isValueInArray: function (column, arry) {
            var indx,
                len = arry && arry.length || 0;
            for (indx = 0; indx < len; indx++) {
                if (arry[indx][0] === column) {
                    return indx;
                }
            }
            return -1;
        },

        formatFloat: function (str, table) {
            if (typeof str !== 'string' || str === '') { return str; }
            // allow using formatFloat without a table; defaults to US number format
            var num,
                usFormat = table && table.config ? table.config.usNumberFormat !== false :
                    typeof table !== 'undefined' ? table : true;
            if (usFormat) {
                // US Format - 1,234,567.89 -> 1234567.89
                str = str.replace(ts.regex.comma, '');
            } else {
                // German Format = 1.234.567,89 -> 1234567.89
                // French Format = 1 234 567,89 -> 1234567.89
                str = str.replace(ts.regex.digitNonUS, '').replace(ts.regex.comma, '.');
            }
            if (ts.regex.digitNegativeTest.test(str)) {
                // make (#) into a negative number -> (10) = -10
                str = str.replace(ts.regex.digitNegativeReplace, '-$1');
            }
            num = parseFloat(str);
            // return the text instead of zero
            return isNaN(num) ? $.trim(str) : num;
        },

        isDigit: function (str) {
            // replace all unwanted chars and match
            return isNaN(str) ?
                ts.regex.digitTest.test(str.toString().replace(ts.regex.digitReplace, '')) :
                str !== '';
        },

        // computeTableHeaderCellIndexes from:
        // http://www.javascripttoolbox.com/lib/table/examples.php
        // http://www.javascripttoolbox.com/temp/table_cellindex.html
        computeColumnIndex: function ($rows, c) {
            var i, j, k, l, cell, cells, rowIndex, rowSpan, colSpan, firstAvailCol,
                // total columns has been calculated, use it to set the matrixrow
                columns = c && c.columns || 0,
                matrix = [],
                matrixrow = new Array(columns);
            for (i = 0; i < $rows.length; i++) {
                cells = $rows[i].cells;
                for (j = 0; j < cells.length; j++) {
                    cell = cells[j];
                    rowIndex = i;
                    rowSpan = cell.rowSpan || 1;
                    colSpan = cell.colSpan || 1;
                    if (typeof matrix[rowIndex] === 'undefined') {
                        matrix[rowIndex] = [];
                    }
                    // Find first available column in the first row
                    for (k = 0; k < matrix[rowIndex].length + 1; k++) {
                        if (typeof matrix[rowIndex][k] === 'undefined') {
                            firstAvailCol = k;
                            break;
                        }
                    }
                    // jscs:disable disallowEmptyBlocks
                    if (columns && cell.cellIndex === firstAvailCol) {
                        // don't to anything
                    } else if (cell.setAttribute) {
                        // jscs:enable disallowEmptyBlocks
                        // add data-column (setAttribute = IE8+)
                        cell.setAttribute('data-column', firstAvailCol);
                    } else {
                        // remove once we drop support for IE7 - 1/12/2016
                        $(cell).attr('data-column', firstAvailCol);
                    }
                    for (k = rowIndex; k < rowIndex + rowSpan; k++) {
                        if (typeof matrix[k] === 'undefined') {
                            matrix[k] = [];
                        }
                        matrixrow = matrix[k];
                        for (l = firstAvailCol; l < firstAvailCol + colSpan; l++) {
                            matrixrow[l] = 'x';
                        }
                    }
                }
            }
            ts.checkColumnCount($rows, matrix, matrixrow.length);
            return matrixrow.length;
        },

        checkColumnCount: function ($rows, matrix, columns) {
            // this DOES NOT report any tbody column issues, except for the math and
            // and column selector widgets
            var i, len,
                valid = true,
                cells = [];
            for (i = 0; i < matrix.length; i++) {
                // some matrix entries are undefined when testing the footer because
                // it is using the rowIndex property
                if (matrix[i]) {
                    len = matrix[i].length;
                    if (matrix[i].length !== columns) {
                        valid = false;
                        break;
                    }
                }
            }
            if (!valid) {
                $rows.each(function (indx, el) {
                    var cell = el.parentElement.nodeName;
                    if (cells.indexOf(cell) < 0) {
                        cells.push(cell);
                    }
                });
                console.error(
                    'Invalid or incorrect number of columns in the ' +
                    cells.join(' or ') + '; expected ' + columns +
                    ', but found ' + len + ' columns'
                );
            }
        },

        // automatically add a colgroup with col elements set to a percentage width
        fixColumnWidth: function (table) {
            table = $(table)[0];
            var overallWidth, percent, $tbodies, len, index,
                c = table.config,
                $colgroup = c.$table.children('colgroup');
            // remove plugin-added colgroup, in case we need to refresh the widths
            if ($colgroup.length && $colgroup.hasClass(ts.css.colgroup)) {
                $colgroup.remove();
            }
            if (c.widthFixed && c.$table.children('colgroup').length === 0) {
                $colgroup = $('<colgroup class="' + ts.css.colgroup + '">');
                overallWidth = c.$table.width();
                // only add col for visible columns - fixes #371
                $tbodies = c.$tbodies.find('tr:first').children(':visible');
                len = $tbodies.length;
                for (index = 0; index < len; index++) {
                    percent = parseInt(($tbodies.eq(index).width() / overallWidth) * 1000, 10) / 10 + '%';
                    $colgroup.append($('<col>').css('width', percent));
                }
                c.$table.prepend($colgroup);
            }
        },

        // get sorter, string, empty, etc options for each column from
        // jQuery data, metadata, header option or header class name ('sorter-false')
        // priority = jQuery data > meta > headers option > header class name
        getData: function (header, configHeader, key) {
            var meta, cl4ss,
                val = '',
                $header = $(header);
            if (!$header.length) { return ''; }
            meta = $.metadata ? $header.metadata() : false;
            cl4ss = ' ' + ($header.attr('class') || '');
            if (typeof $header.data(key) !== 'undefined' ||
                typeof $header.data(key.toLowerCase()) !== 'undefined') {
                // 'data-lockedOrder' is assigned to 'lockedorder'; but 'data-locked-order' is assigned to 'lockedOrder'
                // 'data-sort-initial-order' is assigned to 'sortInitialOrder'
                val += $header.data(key) || $header.data(key.toLowerCase());
            } else if (meta && typeof meta[key] !== 'undefined') {
                val += meta[key];
            } else if (configHeader && typeof configHeader[key] !== 'undefined') {
                val += configHeader[key];
            } else if (cl4ss !== ' ' && cl4ss.match(' ' + key + '-')) {
                // include sorter class name 'sorter-text', etc; now works with 'sorter-my-custom-parser'
                val = cl4ss.match(new RegExp('\\s' + key + '-([\\w-]+)'))[1] || '';
            }
            return $.trim(val);
        },

        getColumnData: function (table, obj, indx, getCell, $headers) {
            if (typeof obj !== 'object' || obj === null) {
                return obj;
            }
            table = $(table)[0];
            var $header, key,
                c = table.config,
                $cells = ($headers || c.$headers),
                // c.$headerIndexed is not defined initially
                $cell = c.$headerIndexed && c.$headerIndexed[indx] ||
                    $cells.find('[data-column="' + indx + '"]:last');
            if (typeof obj[indx] !== 'undefined') {
                return getCell ? obj[indx] : obj[$cells.index($cell)];
            }
            for (key in obj) {
                if (typeof key === 'string') {
                    $header = $cell
                        // header cell with class/id
                        .filter(key)
                        // find elements within the header cell with cell/id
                        .add($cell.find(key));
                    if ($header.length) {
                        return obj[key];
                    }
                }
            }
            return;
        },

        // *** Process table ***
        // add processing indicator
        isProcessing: function ($table, toggle, $headers) {
            $table = $($table);
            var c = $table[0].config,
                // default to all headers
                $header = $headers || $table.find('.' + ts.css.header);
            if (toggle) {
                // don't use sortList if custom $headers used
                if (typeof $headers !== 'undefined' && c.sortList.length > 0) {
                    // get headers from the sortList
                    $header = $header.filter(function () {
                        // get data-column from attr to keep compatibility with jQuery 1.2.6
                        return this.sortDisabled ?
                            false :
                            ts.isValueInArray(parseFloat($(this).attr('data-column')), c.sortList) >= 0;
                    });
                }
                $table.add($header).addClass(ts.css.processing + ' ' + c.cssProcessing);
            } else {
                $table.add($header).removeClass(ts.css.processing + ' ' + c.cssProcessing);
            }
        },

        // detach tbody but save the position
        // don't use tbody because there are portions that look for a tbody index (updateCell)
        processTbody: function (table, $tb, getIt) {
            table = $(table)[0];
            if (getIt) {
                table.isProcessing = true;
                $tb.before('<colgroup class="tablesorter-savemyplace"/>');
                return $.fn.detach ? $tb.detach() : $tb.remove();
            }
            var holdr = $(table).find('colgroup.tablesorter-savemyplace');
            $tb.insertAfter(holdr);
            holdr.remove();
            table.isProcessing = false;
        },

        clearTableBody: function (table) {
            $(table)[0].config.$tbodies.children().detach();
        },

        // used when replacing accented characters during sorting
        characterEquivalents: {
            'a': '\u00e1\u00e0\u00e2\u00e3\u00e4\u0105\u00e5', // áàâãäąå
            'A': '\u00c1\u00c0\u00c2\u00c3\u00c4\u0104\u00c5', // ÁÀÂÃÄĄÅ
            'c': '\u00e7\u0107\u010d', // çćč
            'C': '\u00c7\u0106\u010c', // ÇĆČ
            'e': '\u00e9\u00e8\u00ea\u00eb\u011b\u0119', // éèêëěę
            'E': '\u00c9\u00c8\u00ca\u00cb\u011a\u0118', // ÉÈÊËĚĘ
            'i': '\u00ed\u00ec\u0130\u00ee\u00ef\u0131', // íìİîïı
            'I': '\u00cd\u00cc\u0130\u00ce\u00cf', // ÍÌİÎÏ
            'o': '\u00f3\u00f2\u00f4\u00f5\u00f6\u014d', // óòôõöō
            'O': '\u00d3\u00d2\u00d4\u00d5\u00d6\u014c', // ÓÒÔÕÖŌ
            'ss': '\u00df', // ß (s sharp)
            'SS': '\u1e9e', // ẞ (Capital sharp s)
            'u': '\u00fa\u00f9\u00fb\u00fc\u016f', // úùûüů
            'U': '\u00da\u00d9\u00db\u00dc\u016e' // ÚÙÛÜŮ
        },

        replaceAccents: function (str) {
            var chr,
                acc = '[',
                eq = ts.characterEquivalents;
            if (!ts.characterRegex) {
                ts.characterRegexArray = {};
                for (chr in eq) {
                    if (typeof chr === 'string') {
                        acc += eq[chr];
                        ts.characterRegexArray[chr] = new RegExp('[' + eq[chr] + ']', 'g');
                    }
                }
                ts.characterRegex = new RegExp(acc + ']');
            }
            if (ts.characterRegex.test(str)) {
                for (chr in eq) {
                    if (typeof chr === 'string') {
                        str = str.replace(ts.characterRegexArray[chr], chr);
                    }
                }
            }
            return str;
        },

        validateOptions: function (c) {
            var setting, setting2, typ, timer,
                // ignore options containing an array
                ignore = 'headers sortForce sortList sortAppend widgets'.split(' '),
                orig = c.originalSettings;
            if (orig) {
                if (ts.debug(c, 'core')) {
                    timer = new Date();
                }
                for (setting in orig) {
                    typ = typeof ts.defaults[setting];
                    if (typ === 'undefined') {
                        console.warn('Tablesorter Warning! "table.config.' + setting + '" option not recognized');
                    } else if (typ === 'object') {
                        for (setting2 in orig[setting]) {
                            typ = ts.defaults[setting] && typeof ts.defaults[setting][setting2];
                            if ($.inArray(setting, ignore) < 0 && typ === 'undefined') {
                                console.warn('Tablesorter Warning! "table.config.' + setting + '.' + setting2 + '" option not recognized');
                            }
                        }
                    }
                }
                if (ts.debug(c, 'core')) {
                    console.log('validate options time:' + ts.benchmark(timer));
                }
            }
        },

        // restore headers
        restoreHeaders: function (table) {
            var index, $cell,
                c = $(table)[0].config,
                $headers = c.$table.find(c.selectorHeaders),
                len = $headers.length;
            // don't use c.$headers here in case header cells were swapped
            for (index = 0; index < len; index++) {
                $cell = $headers.eq(index);
                // only restore header cells if it is wrapped
                // because this is also used by the updateAll method
                if ($cell.find('.' + ts.css.headerIn).length) {
                    $cell.html(c.headerContent[index]);
                }
            }
        },

        destroy: function (table, removeClasses, callback) {
            table = $(table)[0];
            if (!table.hasInitialized) { return; }
            // remove all widgets
            ts.removeWidget(table, true, false);
            var events,
                $t = $(table),
                c = table.config,
                $h = $t.find('thead:first'),
                $r = $h.find('tr.' + ts.css.headerRow).removeClass(ts.css.headerRow + ' ' + c.cssHeaderRow),
                $f = $t.find('tfoot:first > tr').children('th, td');
            if (removeClasses === false && $.inArray('uitheme', c.widgets) >= 0) {
                // reapply uitheme classes, in case we want to maintain appearance
                $t.triggerHandler('applyWidgetId', ['uitheme']);
                $t.triggerHandler('applyWidgetId', ['zebra']);
            }
            // remove widget added rows, just in case
            $h.find('tr').not($r).remove();
            // disable tablesorter - not using .unbind( namespace ) because namespacing was
            // added in jQuery v1.4.3 - see http://api.jquery.com/event.namespace/
            events = 'sortReset update updateRows updateAll updateHeaders updateCell addRows updateComplete sorton ' +
                'appendCache updateCache applyWidgetId applyWidgets refreshWidgets removeWidget destroy mouseup mouseleave ' +
                'keypress sortBegin sortEnd resetToLoadState '.split(' ')
                    .join(c.namespace + ' ');
            $t
                .removeData('tablesorter')
                .unbind(events.replace(ts.regex.spaces, ' '));
            c.$headers
                .add($f)
                .removeClass([ts.css.header, c.cssHeader, c.cssAsc, c.cssDesc, ts.css.sortAsc, ts.css.sortDesc, ts.css.sortNone].join(' '))
                .removeAttr('data-column')
                .removeAttr('aria-label')
                .attr('aria-disabled', 'true');
            $r
                .find(c.selectorSort)
                .unbind(('mousedown mouseup keypress '.split(' ').join(c.namespace + ' ')).replace(ts.regex.spaces, ' '));
            ts.restoreHeaders(table);
            $t.toggleClass(ts.css.table + ' ' + c.tableClass + ' tablesorter-' + c.theme, removeClasses === false);
            $t.removeClass(c.namespace.slice(1));
            // clear flag in case the plugin is initialized again
            table.hasInitialized = false;
            delete table.config.cache;
            if (typeof callback === 'function') {
                callback(table);
            }
            if (ts.debug(c, 'core')) {
                console.log('tablesorter has been removed');
            }
        }

    };

    $.fn.tablesorter = function (settings) {
        return this.each(function () {
            var table = this,
                // merge & extend config options
                c = $.extend(true, {}, ts.defaults, settings, ts.instanceMethods);
            // save initial settings
            c.originalSettings = settings;
            // create a table from data (build table widget)
            if (!table.hasInitialized && ts.buildTable && this.nodeName !== 'TABLE') {
                // return the table (in case the original target is the table's container)
                ts.buildTable(table, c);
            } else {
                ts.setup(table, c);
            }
        });
    };

    // set up debug logs
    if (!(window.console && window.console.log)) {
        // access $.tablesorter.logs for browsers that don't have a console...
        ts.logs = [];
        /*jshint -W020 */
        console = {};
        console.log = console.warn = console.error = console.table = function () {
            var arg = arguments.length > 1 ? arguments : arguments[0];
            ts.logs[ts.logs.length] = { date: Date.now(), log: arg };
        };
    }

    // add default parsers
    ts.addParser({
        id: 'no-parser',
        is: function () {
            return false;
        },
        format: function () {
            return '';
        },
        type: 'text'
    });

    ts.addParser({
        id: 'text',
        is: function () {
            return true;
        },
        format: function (str, table) {
            var c = table.config;
            if (str) {
                str = $.trim(c.ignoreCase ? str.toLocaleLowerCase() : str);
                str = c.sortLocaleCompare ? ts.replaceAccents(str) : str;
            }
            return str;
        },
        type: 'text'
    });

    ts.regex.nondigit = /[^\w,. \-()]/g;
    ts.addParser({
        id: 'digit',
        is: function (str) {
            return ts.isDigit(str);
        },
        format: function (str, table) {
            var num = ts.formatFloat((str || '').replace(ts.regex.nondigit, ''), table);
            return str && typeof num === 'number' ? num :
                str ? $.trim(str && table.config.ignoreCase ? str.toLocaleLowerCase() : str) : str;
        },
        type: 'numeric'
    });

    ts.regex.currencyReplace = /[+\-,. ]/g;
    ts.regex.currencyTest = /^\(?\d+[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]|[\u00a3$\u20ac\u00a4\u00a5\u00a2?.]\d+\)?$/;
    ts.addParser({
        id: 'currency',
        is: function (str) {
            str = (str || '').replace(ts.regex.currencyReplace, '');
            // test for £$€¤¥¢
            return ts.regex.currencyTest.test(str);
        },
        format: function (str, table) {
            var num = ts.formatFloat((str || '').replace(ts.regex.nondigit, ''), table);
            return str && typeof num === 'number' ? num :
                str ? $.trim(str && table.config.ignoreCase ? str.toLocaleLowerCase() : str) : str;
        },
        type: 'numeric'
    });

    // too many protocols to add them all https://en.wikipedia.org/wiki/URI_scheme
    // now, this regex can be updated before initialization
    ts.regex.urlProtocolTest = /^(https?|ftp|file):\/\//;
    ts.regex.urlProtocolReplace = /(https?|ftp|file):\/\/(www\.)?/;
    ts.addParser({
        id: 'url',
        is: function (str) {
            return ts.regex.urlProtocolTest.test(str);
        },
        format: function (str) {
            return str ? $.trim(str.replace(ts.regex.urlProtocolReplace, '')) : str;
        },
        type: 'text'
    });

    ts.regex.dash = /-/g;
    ts.regex.isoDate = /^\d{4}[\/\-]\d{1,2}[\/\-]\d{1,2}/;
    ts.addParser({
        id: 'isoDate',
        is: function (str) {
            return ts.regex.isoDate.test(str);
        },
        format: function (str) {
            var date = str ? new Date(str.replace(ts.regex.dash, '/')) : str;
            return date instanceof Date && isFinite(date) ? date.getTime() : str;
        },
        type: 'numeric'
    });

    ts.regex.percent = /%/g;
    ts.regex.percentTest = /(\d\s*?%|%\s*?\d)/;
    ts.addParser({
        id: 'percent',
        is: function (str) {
            return ts.regex.percentTest.test(str) && str.length < 15;
        },
        format: function (str, table) {
            return str ? ts.formatFloat(str.replace(ts.regex.percent, ''), table) : str;
        },
        type: 'numeric'
    });

    // added image parser to core v2.17.9
    ts.addParser({
        id: 'image',
        is: function (str, table, node, $node) {
            return $node.find('img').length > 0;
        },
        format: function (str, table, cell) {
            return $(cell).find('img').attr(table.config.imgAttr || 'alt') || str;
        },
        parsed: true, // filter widget flag
        type: 'text'
    });

    ts.regex.dateReplace = /(\S)([AP]M)$/i; // used by usLongDate & time parser
    ts.regex.usLongDateTest1 = /^[A-Z]{3,10}\.?\s+\d{1,2},?\s+(\d{4})(\s+\d{1,2}:\d{2}(:\d{2})?(\s+[AP]M)?)?$/i;
    ts.regex.usLongDateTest2 = /^\d{1,2}\s+[A-Z]{3,10}\s+\d{4}/i;
    ts.addParser({
        id: 'usLongDate',
        is: function (str) {
            // two digit years are not allowed cross-browser
            // Jan 01, 2013 12:34:56 PM or 01 Jan 2013
            return ts.regex.usLongDateTest1.test(str) || ts.regex.usLongDateTest2.test(str);
        },
        format: function (str) {
            var date = str ? new Date(str.replace(ts.regex.dateReplace, '$1 $2')) : str;
            return date instanceof Date && isFinite(date) ? date.getTime() : str;
        },
        type: 'numeric'
    });

    // testing for ##-##-#### or ####-##-##, so it's not perfect; time can be included
    ts.regex.shortDateTest = /(^\d{1,2}[\/\s]\d{1,2}[\/\s]\d{4})|(^\d{4}[\/\s]\d{1,2}[\/\s]\d{1,2})/;
    // escaped "-" because JSHint in Firefox was showing it as an error
    ts.regex.shortDateReplace = /[\-.,]/g;
    // XXY covers MDY & DMY formats
    ts.regex.shortDateXXY = /(\d{1,2})[\/\s](\d{1,2})[\/\s](\d{4})/;
    ts.regex.shortDateYMD = /(\d{4})[\/\s](\d{1,2})[\/\s](\d{1,2})/;
    ts.convertFormat = function (dateString, format) {
        dateString = (dateString || '')
            .replace(ts.regex.spaces, ' ')
            .replace(ts.regex.shortDateReplace, '/');
        if (format === 'mmddyyyy') {
            dateString = dateString.replace(ts.regex.shortDateXXY, '$3/$1/$2');
        } else if (format === 'ddmmyyyy') {
            dateString = dateString.replace(ts.regex.shortDateXXY, '$3/$2/$1');
        } else if (format === 'yyyymmdd') {
            dateString = dateString.replace(ts.regex.shortDateYMD, '$1/$2/$3');
        }
        var date = new Date(dateString);
        return date instanceof Date && isFinite(date) ? date.getTime() : '';
    };

    ts.addParser({
        id: 'shortDate', // 'mmddyyyy', 'ddmmyyyy' or 'yyyymmdd'
        is: function (str) {
            str = (str || '').replace(ts.regex.spaces, ' ').replace(ts.regex.shortDateReplace, '/');
            return ts.regex.shortDateTest.test(str);
        },
        format: function (str, table, cell, cellIndex) {
            if (str) {
                var c = table.config,
                    $header = c.$headerIndexed[cellIndex],
                    format = $header.length && $header.data('dateFormat') ||
                        ts.getData($header, ts.getColumnData(table, c.headers, cellIndex), 'dateFormat') ||
                        c.dateFormat;
                // save format because getData can be slow...
                if ($header.length) {
                    $header.data('dateFormat', format);
                }
                return ts.convertFormat(str, format) || str;
            }
            return str;
        },
        type: 'numeric'
    });

    // match 24 hour time & 12 hours time + am/pm - see http://regexr.com/3c3tk
    ts.regex.timeTest = /^(0?[1-9]|1[0-2]):([0-5]\d)(\s[AP]M)$|^((?:[01]\d|[2][0-4]):[0-5]\d)$/i;
    ts.regex.timeMatch = /(0?[1-9]|1[0-2]):([0-5]\d)(\s[AP]M)|((?:[01]\d|[2][0-4]):[0-5]\d)/i;
    ts.addParser({
        id: 'time',
        is: function (str) {
            return ts.regex.timeTest.test(str);
        },
        format: function (str) {
            // isolate time... ignore month, day and year
            var temp,
                timePart = (str || '').match(ts.regex.timeMatch),
                orig = new Date(str),
                // no time component? default to 00:00 by leaving it out, but only if str is defined
                time = str && (timePart !== null ? timePart[0] : '00:00 AM'),
                date = time ? new Date('2000/01/01 ' + time.replace(ts.regex.dateReplace, '$1 $2')) : time;
            if (date instanceof Date && isFinite(date)) {
                temp = orig instanceof Date && isFinite(orig) ? orig.getTime() : 0;
                // if original string was a valid date, add it to the decimal so the column sorts in some kind of order
                // luckily new Date() ignores the decimals
                return temp ? parseFloat(date.getTime() + '.' + orig.getTime()) : date.getTime();
            }
            return str;
        },
        type: 'numeric'
    });

    ts.addParser({
        id: 'metadata',
        is: function () {
            return false;
        },
        format: function (str, table, cell) {
            var c = table.config,
                p = (!c.parserMetadataName) ? 'sortValue' : c.parserMetadataName;
            return $(cell).metadata()[p];
        },
        type: 'numeric'
    });

	/*
		██████ ██████ █████▄ █████▄ ▄████▄
		  ▄█▀  ██▄▄   ██▄▄██ ██▄▄██ ██▄▄██
		▄█▀    ██▀▀   ██▀▀██ ██▀▀█  ██▀▀██
		██████ ██████ █████▀ ██  ██ ██  ██
		*/
    // add default widgets
    ts.addWidget({
        id: 'zebra',
        priority: 90,
        format: function (table, c, wo) {
            var $visibleRows, $row, count, isEven, tbodyIndex, rowIndex, len,
                child = new RegExp(c.cssChildRow, 'i'),
                $tbodies = c.$tbodies.add($(c.namespace + '_extra_table').children('tbody:not(.' + c.cssInfoBlock + ')'));
            for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++) {
                // loop through the visible rows
                count = 0;
                $visibleRows = $tbodies.eq(tbodyIndex).children('tr:visible').not(c.selectorRemove);
                len = $visibleRows.length;
                for (rowIndex = 0; rowIndex < len; rowIndex++) {
                    $row = $visibleRows.eq(rowIndex);
                    // style child rows the same way the parent row was styled
                    if (!child.test($row[0].className)) { count++; }
                    isEven = (count % 2 === 0);
                    $row
                        .removeClass(wo.zebra[isEven ? 1 : 0])
                        .addClass(wo.zebra[isEven ? 0 : 1]);
                }
            }
        },
        remove: function (table, c, wo, refreshing) {
            if (refreshing) { return; }
            var tbodyIndex, $tbody,
                $tbodies = c.$tbodies,
                toRemove = (wo.zebra || ['even', 'odd']).join(' ');
            for (tbodyIndex = 0; tbodyIndex < $tbodies.length; tbodyIndex++) {
                $tbody = ts.processTbody(table, $tbodies.eq(tbodyIndex), true); // remove tbody
                $tbody.children().removeClass(toRemove);
                ts.processTbody(table, $tbody, false); // restore tbody
            }
        }
    });

})(jQuery);
;
/*
 * This combined file was created by the DataTables downloader builder:
 *   https://datatables.net/download
 *
 * To rebuild or modify this file with the latest versions of the included
 * software please visit:
 *   https://datatables.net/download/#dt/dt-1.10.12,af-2.1.2,b-1.2.1,b-colvis-1.2.1,cr-1.3.2,fc-3.2.2,fh-3.1.2,kt-2.1.2,r-2.1.0,rr-1.1.2,sc-1.4.2,se-1.2.0
 *
 * Included libraries:
 *   DataTables 1.10.12, AutoFill 2.1.2, Buttons 1.2.1, Column visibility 1.2.1, ColReorder 1.3.2, FixedColumns 3.2.2, FixedHeader 3.1.2, KeyTable 2.1.2, Responsive 2.1.0, RowReorder 1.1.2, Scroller 1.4.2, Select 1.2.0
 */

/*! DataTables 1.10.12
 * ©2008-2015 SpryMedia Ltd - datatables.net/license
 */

/**
 * @summary     DataTables
 * @description Paginate, search and order HTML tables
 * @version     1.10.12
 * @file        jquery.dataTables.js
 * @author      SpryMedia Ltd (www.sprymedia.co.uk)
 * @contact     www.sprymedia.co.uk/contact
 * @copyright   Copyright 2008-2015 SpryMedia Ltd.
 *
 * This source file is free software, available under the following license:
 *   MIT license - http://datatables.net/license
 *
 * This source file is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
 *
 * For details please refer to: http://www.datatables.net
 */

/*jslint evil: true, undef: true, browser: true */
/*globals $,require,jQuery,define,_selector_run,_selector_opts,_selector_first,_selector_row_indexes,_ext,_Api,_api_register,_api_registerPlural,_re_new_lines,_re_html,_re_formatted_numeric,_re_escape_regex,_empty,_intVal,_numToDecimal,_isNumber,_isHtml,_htmlNumeric,_pluck,_pluck_order,_range,_stripHtml,_unique,_fnBuildAjax,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnAjaxDataSrc,_fnAddColumn,_fnColumnOptions,_fnAdjustColumnSizing,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnVisbleColumns,_fnGetColumns,_fnColumnTypes,_fnApplyColumnDefs,_fnHungarianMap,_fnCamelToHungarian,_fnLanguageCompat,_fnBrowserDetect,_fnAddData,_fnAddTr,_fnNodeToDataIndex,_fnNodeToColumnIndex,_fnGetCellData,_fnSetCellData,_fnSplitObjNotation,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnGetDataMaster,_fnClearTable,_fnDeleteIndex,_fnInvalidate,_fnGetRowElements,_fnCreateTr,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAddOptionsHtml,_fnDetectHeader,_fnGetUniqueThs,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnFilterCreateSearch,_fnEscapeRegex,_fnFilterData,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnInfoMacros,_fnInitialise,_fnInitComplete,_fnLengthChange,_fnFeatureHtmlLength,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnFeatureHtmlTable,_fnScrollDraw,_fnApplyToChildren,_fnCalculateColumnWidths,_fnThrottle,_fnConvertToWidth,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnSortFlatten,_fnSort,_fnSortAria,_fnSortListener,_fnSortAttachListener,_fnSortingClasses,_fnSortData,_fnSaveState,_fnLoadState,_fnSettingsFromNode,_fnLog,_fnMap,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnLengthOverflow,_fnRenderer,_fnDataSource,_fnRowAttributes*/

(function( factory ) {
	"use strict";

	if ( typeof define === 'function' && define.amd ) {
		// AMD
		define( ['jquery'], function ( $ ) {
			return factory( $, window, document );
		} );
	}
	else if ( typeof exports === 'object' ) {
		// CommonJS
		module.exports = function (root, $) {
			if ( ! root ) {
				// CommonJS environments without a window global must pass a
				// root. This will give an error otherwise
				root = window;
			}

			if ( ! $ ) {
				$ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window
					require('jquery') :
					require('jquery')( root );
			}

			return factory( $, root, root.document );
		};
	}
	else {
		// Browser
		factory( jQuery, window, document );
	}
}
(function( $, window, document, undefined ) {
	"use strict";

	/**
	 * DataTables is a plug-in for the jQuery Javascript library. It is a highly
	 * flexible tool, based upon the foundations of progressive enhancement,
	 * which will add advanced interaction controls to any HTML table. For a
	 * full list of features please refer to
	 * [DataTables.net](href="http://datatables.net).
	 *
	 * Note that the `DataTable` object is not a global variable but is aliased
	 * to `jQuery.fn.DataTable` and `jQuery.fn.dataTable` through which it may
	 * be  accessed.
	 *
	 *  @class
	 *  @param {object} [init={}] Configuration object for DataTables. Options
	 *    are defined by {@link DataTable.defaults}
	 *  @requires jQuery 1.7+
	 *
	 *  @example
	 *    // Basic initialisation
	 *    $(document).ready( function {
	 *      $('#example').dataTable();
	 *    } );
	 *
	 *  @example
	 *    // Initialisation with configuration options - in this case, disable
	 *    // pagination and sorting.
	 *    $(document).ready( function {
	 *      $('#example').dataTable( {
	 *        "paginate": false,
	 *        "sort": false
	 *      } );
	 *    } );
	 */
	var DataTable = function ( options )
	{
		/**
		 * Perform a jQuery selector action on the table's TR elements (from the tbody) and
		 * return the resulting jQuery object.
		 *  @param {string|node|jQuery} sSelector jQuery selector or node collection to act on
		 *  @param {object} [oOpts] Optional parameters for modifying the rows to be included
		 *  @param {string} [oOpts.filter=none] Select TR elements that meet the current filter
		 *    criterion ("applied") or all TR elements (i.e. no filter).
		 *  @param {string} [oOpts.order=current] Order of the TR elements in the processed array.
		 *    Can be either 'current', whereby the current sorting of the table is used, or
		 *    'original' whereby the original order the data was read into the table is used.
		 *  @param {string} [oOpts.page=all] Limit the selection to the currently displayed page
		 *    ("current") or not ("all"). If 'current' is given, then order is assumed to be
		 *    'current' and filter is 'applied', regardless of what they might be given as.
		 *  @returns {object} jQuery object, filtered by the given selector.
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Highlight every second row
		 *      oTable.$('tr:odd').css('backgroundColor', 'blue');
		 *    } );
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Filter to rows with 'Webkit' in them, add a background colour and then
		 *      // remove the filter, thus highlighting the 'Webkit' rows only.
		 *      oTable.fnFilter('Webkit');
		 *      oTable.$('tr', {"search": "applied"}).css('backgroundColor', 'blue');
		 *      oTable.fnFilter('');
		 *    } );
		 */
		this.$ = function ( sSelector, oOpts )
		{
			return this.api(true).$( sSelector, oOpts );
		};
		
		
		/**
		 * Almost identical to $ in operation, but in this case returns the data for the matched
		 * rows - as such, the jQuery selector used should match TR row nodes or TD/TH cell nodes
		 * rather than any descendants, so the data can be obtained for the row/cell. If matching
		 * rows are found, the data returned is the original data array/object that was used to
		 * create the row (or a generated array if from a DOM source).
		 *
		 * This method is often useful in-combination with $ where both functions are given the
		 * same parameters and the array indexes will match identically.
		 *  @param {string|node|jQuery} sSelector jQuery selector or node collection to act on
		 *  @param {object} [oOpts] Optional parameters for modifying the rows to be included
		 *  @param {string} [oOpts.filter=none] Select elements that meet the current filter
		 *    criterion ("applied") or all elements (i.e. no filter).
		 *  @param {string} [oOpts.order=current] Order of the data in the processed array.
		 *    Can be either 'current', whereby the current sorting of the table is used, or
		 *    'original' whereby the original order the data was read into the table is used.
		 *  @param {string} [oOpts.page=all] Limit the selection to the currently displayed page
		 *    ("current") or not ("all"). If 'current' is given, then order is assumed to be
		 *    'current' and filter is 'applied', regardless of what they might be given as.
		 *  @returns {array} Data for the matched elements. If any elements, as a result of the
		 *    selector, were not TR, TD or TH elements in the DataTable, they will have a null
		 *    entry in the array.
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Get the data from the first row in the table
		 *      var data = oTable._('tr:first');
		 *
		 *      // Do something useful with the data
		 *      alert( "First cell is: "+data[0] );
		 *    } );
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Filter to 'Webkit' and get all data for
		 *      oTable.fnFilter('Webkit');
		 *      var data = oTable._('tr', {"search": "applied"});
		 *
		 *      // Do something with the data
		 *      alert( data.length+" rows matched the search" );
		 *    } );
		 */
		this._ = function ( sSelector, oOpts )
		{
			return this.api(true).rows( sSelector, oOpts ).data();
		};
		
		
		/**
		 * Create a DataTables Api instance, with the currently selected tables for
		 * the Api's context.
		 * @param {boolean} [traditional=false] Set the API instance's context to be
		 *   only the table referred to by the `DataTable.ext.iApiIndex` option, as was
		 *   used in the API presented by DataTables 1.9- (i.e. the traditional mode),
		 *   or if all tables captured in the jQuery object should be used.
		 * @return {DataTables.Api}
		 */
		this.api = function ( traditional )
		{
			return traditional ?
				new _Api(
					_fnSettingsFromNode( this[ _ext.iApiIndex ] )
				) :
				new _Api( this );
		};
		
		
		/**
		 * Add a single new row or multiple rows of data to the table. Please note
		 * that this is suitable for client-side processing only - if you are using
		 * server-side processing (i.e. "bServerSide": true), then to add data, you
		 * must add it to the data source, i.e. the server-side, through an Ajax call.
		 *  @param {array|object} data The data to be added to the table. This can be:
		 *    <ul>
		 *      <li>1D array of data - add a single row with the data provided</li>
		 *      <li>2D array of arrays - add multiple rows in a single call</li>
		 *      <li>object - data object when using <i>mData</i></li>
		 *      <li>array of objects - multiple data objects when using <i>mData</i></li>
		 *    </ul>
		 *  @param {bool} [redraw=true] redraw the table or not
		 *  @returns {array} An array of integers, representing the list of indexes in
		 *    <i>aoData</i> ({@link DataTable.models.oSettings}) that have been added to
		 *    the table.
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    // Global var for counter
		 *    var giCount = 2;
		 *
		 *    $(document).ready(function() {
		 *      $('#example').dataTable();
		 *    } );
		 *
		 *    function fnClickAddRow() {
		 *      $('#example').dataTable().fnAddData( [
		 *        giCount+".1",
		 *        giCount+".2",
		 *        giCount+".3",
		 *        giCount+".4" ]
		 *      );
		 *
		 *      giCount++;
		 *    }
		 */
		this.fnAddData = function( data, redraw )
		{
			var api = this.api( true );
		
			/* Check if we want to add multiple rows or not */
			var rows = $.isArray(data) && ( $.isArray(data[0]) || $.isPlainObject(data[0]) ) ?
				api.rows.add( data ) :
				api.row.add( data );
		
			if ( redraw === undefined || redraw ) {
				api.draw();
			}
		
			return rows.flatten().toArray();
		};
		
		
		/**
		 * This function will make DataTables recalculate the column sizes, based on the data
		 * contained in the table and the sizes applied to the columns (in the DOM, CSS or
		 * through the sWidth parameter). This can be useful when the width of the table's
		 * parent element changes (for example a window resize).
		 *  @param {boolean} [bRedraw=true] Redraw the table or not, you will typically want to
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable( {
		 *        "sScrollY": "200px",
		 *        "bPaginate": false
		 *      } );
		 *
		 *      $(window).bind('resize', function () {
		 *        oTable.fnAdjustColumnSizing();
		 *      } );
		 *    } );
		 */
		this.fnAdjustColumnSizing = function ( bRedraw )
		{
			var api = this.api( true ).columns.adjust();
			var settings = api.settings()[0];
			var scroll = settings.oScroll;
		
			if ( bRedraw === undefined || bRedraw ) {
				api.draw( false );
			}
			else if ( scroll.sX !== "" || scroll.sY !== "" ) {
				/* If not redrawing, but scrolling, we want to apply the new column sizes anyway */
				_fnScrollDraw( settings );
			}
		};
		
		
		/**
		 * Quickly and simply clear a table
		 *  @param {bool} [bRedraw=true] redraw the table or not
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Immediately 'nuke' the current rows (perhaps waiting for an Ajax callback...)
		 *      oTable.fnClearTable();
		 *    } );
		 */
		this.fnClearTable = function( bRedraw )
		{
			var api = this.api( true ).clear();
		
			if ( bRedraw === undefined || bRedraw ) {
				api.draw();
			}
		};
		
		
		/**
		 * The exact opposite of 'opening' a row, this function will close any rows which
		 * are currently 'open'.
		 *  @param {node} nTr the table row to 'close'
		 *  @returns {int} 0 on success, or 1 if failed (can't find the row)
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable;
		 *
		 *      // 'open' an information row when a row is clicked on
		 *      $('#example tbody tr').click( function () {
		 *        if ( oTable.fnIsOpen(this) ) {
		 *          oTable.fnClose( this );
		 *        } else {
		 *          oTable.fnOpen( this, "Temporary row opened", "info_row" );
		 *        }
		 *      } );
		 *
		 *      oTable = $('#example').dataTable();
		 *    } );
		 */
		this.fnClose = function( nTr )
		{
			this.api( true ).row( nTr ).child.hide();
		};
		
		
		/**
		 * Remove a row for the table
		 *  @param {mixed} target The index of the row from aoData to be deleted, or
		 *    the TR element you want to delete
		 *  @param {function|null} [callBack] Callback function
		 *  @param {bool} [redraw=true] Redraw the table or not
		 *  @returns {array} The row that was deleted
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Immediately remove the first row
		 *      oTable.fnDeleteRow( 0 );
		 *    } );
		 */
		this.fnDeleteRow = function( target, callback, redraw )
		{
			var api = this.api( true );
			var rows = api.rows( target );
			var settings = rows.settings()[0];
			var data = settings.aoData[ rows[0][0] ];
		
			rows.remove();
		
			if ( callback ) {
				callback.call( this, settings, data );
			}
		
			if ( redraw === undefined || redraw ) {
				api.draw();
			}
		
			return data;
		};
		
		
		/**
		 * Restore the table to it's original state in the DOM by removing all of DataTables
		 * enhancements, alterations to the DOM structure of the table and event listeners.
		 *  @param {boolean} [remove=false] Completely remove the table from the DOM
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      // This example is fairly pointless in reality, but shows how fnDestroy can be used
		 *      var oTable = $('#example').dataTable();
		 *      oTable.fnDestroy();
		 *    } );
		 */
		this.fnDestroy = function ( remove )
		{
			this.api( true ).destroy( remove );
		};
		
		
		/**
		 * Redraw the table
		 *  @param {bool} [complete=true] Re-filter and resort (if enabled) the table before the draw.
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Re-draw the table - you wouldn't want to do it here, but it's an example :-)
		 *      oTable.fnDraw();
		 *    } );
		 */
		this.fnDraw = function( complete )
		{
			// Note that this isn't an exact match to the old call to _fnDraw - it takes
			// into account the new data, but can hold position.
			this.api( true ).draw( complete );
		};
		
		
		/**
		 * Filter the input based on data
		 *  @param {string} sInput String to filter the table on
		 *  @param {int|null} [iColumn] Column to limit filtering to
		 *  @param {bool} [bRegex=false] Treat as regular expression or not
		 *  @param {bool} [bSmart=true] Perform smart filtering or not
		 *  @param {bool} [bShowGlobal=true] Show the input global filter in it's input box(es)
		 *  @param {bool} [bCaseInsensitive=true] Do case-insensitive matching (true) or not (false)
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Sometime later - filter...
		 *      oTable.fnFilter( 'test string' );
		 *    } );
		 */
		this.fnFilter = function( sInput, iColumn, bRegex, bSmart, bShowGlobal, bCaseInsensitive )
		{
			var api = this.api( true );
		
			if ( iColumn === null || iColumn === undefined ) {
				api.search( sInput, bRegex, bSmart, bCaseInsensitive );
			}
			else {
				api.column( iColumn ).search( sInput, bRegex, bSmart, bCaseInsensitive );
			}
		
			api.draw();
		};
		
		
		/**
		 * Get the data for the whole table, an individual row or an individual cell based on the
		 * provided parameters.
		 *  @param {int|node} [src] A TR row node, TD/TH cell node or an integer. If given as
		 *    a TR node then the data source for the whole row will be returned. If given as a
		 *    TD/TH cell node then iCol will be automatically calculated and the data for the
		 *    cell returned. If given as an integer, then this is treated as the aoData internal
		 *    data index for the row (see fnGetPosition) and the data for that row used.
		 *  @param {int} [col] Optional column index that you want the data of.
		 *  @returns {array|object|string} If mRow is undefined, then the data for all rows is
		 *    returned. If mRow is defined, just data for that row, and is iCol is
		 *    defined, only data for the designated cell is returned.
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    // Row data
		 *    $(document).ready(function() {
		 *      oTable = $('#example').dataTable();
		 *
		 *      oTable.$('tr').click( function () {
		 *        var data = oTable.fnGetData( this );
		 *        // ... do something with the array / object of data for the row
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Individual cell data
		 *    $(document).ready(function() {
		 *      oTable = $('#example').dataTable();
		 *
		 *      oTable.$('td').click( function () {
		 *        var sData = oTable.fnGetData( this );
		 *        alert( 'The cell clicked on had the value of '+sData );
		 *      } );
		 *    } );
		 */
		this.fnGetData = function( src, col )
		{
			var api = this.api( true );
		
			if ( src !== undefined ) {
				var type = src.nodeName ? src.nodeName.toLowerCase() : '';
		
				return col !== undefined || type == 'td' || type == 'th' ?
					api.cell( src, col ).data() :
					api.row( src ).data() || null;
			}
		
			return api.data().toArray();
		};
		
		
		/**
		 * Get an array of the TR nodes that are used in the table's body. Note that you will
		 * typically want to use the '$' API method in preference to this as it is more
		 * flexible.
		 *  @param {int} [iRow] Optional row index for the TR element you want
		 *  @returns {array|node} If iRow is undefined, returns an array of all TR elements
		 *    in the table's body, or iRow is defined, just the TR element requested.
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Get the nodes from the table
		 *      var nNodes = oTable.fnGetNodes( );
		 *    } );
		 */
		this.fnGetNodes = function( iRow )
		{
			var api = this.api( true );
		
			return iRow !== undefined ?
				api.row( iRow ).node() :
				api.rows().nodes().flatten().toArray();
		};
		
		
		/**
		 * Get the array indexes of a particular cell from it's DOM element
		 * and column index including hidden columns
		 *  @param {node} node this can either be a TR, TD or TH in the table's body
		 *  @returns {int} If nNode is given as a TR, then a single index is returned, or
		 *    if given as a cell, an array of [row index, column index (visible),
		 *    column index (all)] is given.
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      $('#example tbody td').click( function () {
		 *        // Get the position of the current data from the node
		 *        var aPos = oTable.fnGetPosition( this );
		 *
		 *        // Get the data array for this row
		 *        var aData = oTable.fnGetData( aPos[0] );
		 *
		 *        // Update the data array and return the value
		 *        aData[ aPos[1] ] = 'clicked';
		 *        this.innerHTML = 'clicked';
		 *      } );
		 *
		 *      // Init DataTables
		 *      oTable = $('#example').dataTable();
		 *    } );
		 */
		this.fnGetPosition = function( node )
		{
			var api = this.api( true );
			var nodeName = node.nodeName.toUpperCase();
		
			if ( nodeName == 'TR' ) {
				return api.row( node ).index();
			}
			else if ( nodeName == 'TD' || nodeName == 'TH' ) {
				var cell = api.cell( node ).index();
		
				return [
					cell.row,
					cell.columnVisible,
					cell.column
				];
			}
			return null;
		};
		
		
		/**
		 * Check to see if a row is 'open' or not.
		 *  @param {node} nTr the table row to check
		 *  @returns {boolean} true if the row is currently open, false otherwise
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable;
		 *
		 *      // 'open' an information row when a row is clicked on
		 *      $('#example tbody tr').click( function () {
		 *        if ( oTable.fnIsOpen(this) ) {
		 *          oTable.fnClose( this );
		 *        } else {
		 *          oTable.fnOpen( this, "Temporary row opened", "info_row" );
		 *        }
		 *      } );
		 *
		 *      oTable = $('#example').dataTable();
		 *    } );
		 */
		this.fnIsOpen = function( nTr )
		{
			return this.api( true ).row( nTr ).child.isShown();
		};
		
		
		/**
		 * This function will place a new row directly after a row which is currently
		 * on display on the page, with the HTML contents that is passed into the
		 * function. This can be used, for example, to ask for confirmation that a
		 * particular record should be deleted.
		 *  @param {node} nTr The table row to 'open'
		 *  @param {string|node|jQuery} mHtml The HTML to put into the row
		 *  @param {string} sClass Class to give the new TD cell
		 *  @returns {node} The row opened. Note that if the table row passed in as the
		 *    first parameter, is not found in the table, this method will silently
		 *    return.
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable;
		 *
		 *      // 'open' an information row when a row is clicked on
		 *      $('#example tbody tr').click( function () {
		 *        if ( oTable.fnIsOpen(this) ) {
		 *          oTable.fnClose( this );
		 *        } else {
		 *          oTable.fnOpen( this, "Temporary row opened", "info_row" );
		 *        }
		 *      } );
		 *
		 *      oTable = $('#example').dataTable();
		 *    } );
		 */
		this.fnOpen = function( nTr, mHtml, sClass )
		{
			return this.api( true )
				.row( nTr )
				.child( mHtml, sClass )
				.show()
				.child()[0];
		};
		
		
		/**
		 * Change the pagination - provides the internal logic for pagination in a simple API
		 * function. With this function you can have a DataTables table go to the next,
		 * previous, first or last pages.
		 *  @param {string|int} mAction Paging action to take: "first", "previous", "next" or "last"
		 *    or page number to jump to (integer), note that page 0 is the first page.
		 *  @param {bool} [bRedraw=true] Redraw the table or not
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *      oTable.fnPageChange( 'next' );
		 *    } );
		 */
		this.fnPageChange = function ( mAction, bRedraw )
		{
			var api = this.api( true ).page( mAction );
		
			if ( bRedraw === undefined || bRedraw ) {
				api.draw(false);
			}
		};
		
		
		/**
		 * Show a particular column
		 *  @param {int} iCol The column whose display should be changed
		 *  @param {bool} bShow Show (true) or hide (false) the column
		 *  @param {bool} [bRedraw=true] Redraw the table or not
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Hide the second column after initialisation
		 *      oTable.fnSetColumnVis( 1, false );
		 *    } );
		 */
		this.fnSetColumnVis = function ( iCol, bShow, bRedraw )
		{
			var api = this.api( true ).column( iCol ).visible( bShow );
		
			if ( bRedraw === undefined || bRedraw ) {
				api.columns.adjust().draw();
			}
		};
		
		
		/**
		 * Get the settings for a particular table for external manipulation
		 *  @returns {object} DataTables settings object. See
		 *    {@link DataTable.models.oSettings}
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *      var oSettings = oTable.fnSettings();
		 *
		 *      // Show an example parameter from the settings
		 *      alert( oSettings._iDisplayStart );
		 *    } );
		 */
		this.fnSettings = function()
		{
			return _fnSettingsFromNode( this[_ext.iApiIndex] );
		};
		
		
		/**
		 * Sort the table by a particular column
		 *  @param {int} iCol the data index to sort on. Note that this will not match the
		 *    'display index' if you have hidden data entries
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Sort immediately with columns 0 and 1
		 *      oTable.fnSort( [ [0,'asc'], [1,'asc'] ] );
		 *    } );
		 */
		this.fnSort = function( aaSort )
		{
			this.api( true ).order( aaSort ).draw();
		};
		
		
		/**
		 * Attach a sort listener to an element for a given column
		 *  @param {node} nNode the element to attach the sort listener to
		 *  @param {int} iColumn the column that a click on this node will sort on
		 *  @param {function} [fnCallback] callback function when sort is run
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Sort on column 1, when 'sorter' is clicked on
		 *      oTable.fnSortListener( document.getElementById('sorter'), 1 );
		 *    } );
		 */
		this.fnSortListener = function( nNode, iColumn, fnCallback )
		{
			this.api( true ).order.listener( nNode, iColumn, fnCallback );
		};
		
		
		/**
		 * Update a table cell or row - this method will accept either a single value to
		 * update the cell with, an array of values with one element for each column or
		 * an object in the same format as the original data source. The function is
		 * self-referencing in order to make the multi column updates easier.
		 *  @param {object|array|string} mData Data to update the cell/row with
		 *  @param {node|int} mRow TR element you want to update or the aoData index
		 *  @param {int} [iColumn] The column to update, give as null or undefined to
		 *    update a whole row.
		 *  @param {bool} [bRedraw=true] Redraw the table or not
		 *  @param {bool} [bAction=true] Perform pre-draw actions or not
		 *  @returns {int} 0 on success, 1 on error
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *      oTable.fnUpdate( 'Example update', 0, 0 ); // Single cell
		 *      oTable.fnUpdate( ['a', 'b', 'c', 'd', 'e'], $('tbody tr')[0] ); // Row
		 *    } );
		 */
		this.fnUpdate = function( mData, mRow, iColumn, bRedraw, bAction )
		{
			var api = this.api( true );
		
			if ( iColumn === undefined || iColumn === null ) {
				api.row( mRow ).data( mData );
			}
			else {
				api.cell( mRow, iColumn ).data( mData );
			}
		
			if ( bAction === undefined || bAction ) {
				api.columns.adjust();
			}
		
			if ( bRedraw === undefined || bRedraw ) {
				api.draw();
			}
			return 0;
		};
		
		
		/**
		 * Provide a common method for plug-ins to check the version of DataTables being used, in order
		 * to ensure compatibility.
		 *  @param {string} sVersion Version string to check for, in the format "X.Y.Z". Note that the
		 *    formats "X" and "X.Y" are also acceptable.
		 *  @returns {boolean} true if this version of DataTables is greater or equal to the required
		 *    version, or false if this version of DataTales is not suitable
		 *  @method
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *      alert( oTable.fnVersionCheck( '1.9.0' ) );
		 *    } );
		 */
		this.fnVersionCheck = _ext.fnVersionCheck;
		

		var _that = this;
		var emptyInit = options === undefined;
		var len = this.length;

		if ( emptyInit ) {
			options = {};
		}

		this.oApi = this.internal = _ext.internal;

		// Extend with old style plug-in API methods
		for ( var fn in DataTable.ext.internal ) {
			if ( fn ) {
				this[fn] = _fnExternApiFunc(fn);
			}
		}

		this.each(function() {
			// For each initialisation we want to give it a clean initialisation
			// object that can be bashed around
			var o = {};
			var oInit = len > 1 ? // optimisation for single table case
				_fnExtend( o, options, true ) :
				options;

			/*global oInit,_that,emptyInit*/
			var i=0, iLen, j, jLen, k, kLen;
			var sId = this.getAttribute( 'id' );
			var bInitHandedOff = false;
			var defaults = DataTable.defaults;
			var $this = $(this);
			
			
			/* Sanity check */
			if ( this.nodeName.toLowerCase() != 'table' )
			{
				_fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 );
				return;
			}
			
			/* Backwards compatibility for the defaults */
			_fnCompatOpts( defaults );
			_fnCompatCols( defaults.column );
			
			/* Convert the camel-case defaults to Hungarian */
			_fnCamelToHungarian( defaults, defaults, true );
			_fnCamelToHungarian( defaults.column, defaults.column, true );
			
			/* Setting up the initialisation object */
			_fnCamelToHungarian( defaults, $.extend( oInit, $this.data() ) );
			
			
			
			/* Check to see if we are re-initialising a table */
			var allSettings = DataTable.settings;
			for ( i=0, iLen=allSettings.length ; i<iLen ; i++ )
			{
				var s = allSettings[i];
			
				/* Base check on table node */
				if ( s.nTable == this || s.nTHead.parentNode == this || (s.nTFoot && s.nTFoot.parentNode == this) )
				{
					var bRetrieve = oInit.bRetrieve !== undefined ? oInit.bRetrieve : defaults.bRetrieve;
					var bDestroy = oInit.bDestroy !== undefined ? oInit.bDestroy : defaults.bDestroy;
			
					if ( emptyInit || bRetrieve )
					{
						return s.oInstance;
					}
					else if ( bDestroy )
					{
						s.oInstance.fnDestroy();
						break;
					}
					else
					{
						_fnLog( s, 0, 'Cannot reinitialise DataTable', 3 );
						return;
					}
				}
			
				/* If the element we are initialising has the same ID as a table which was previously
				 * initialised, but the table nodes don't match (from before) then we destroy the old
				 * instance by simply deleting it. This is under the assumption that the table has been
				 * destroyed by other methods. Anyone using non-id selectors will need to do this manually
				 */
				if ( s.sTableId == this.id )
				{
					allSettings.splice( i, 1 );
					break;
				}
			}
			
			/* Ensure the table has an ID - required for accessibility */
			if ( sId === null || sId === "" )
			{
				sId = "DataTables_Table_"+(DataTable.ext._unique++);
				this.id = sId;
			}
			
			/* Create the settings object for this table and set some of the default parameters */
			var oSettings = $.extend( true, {}, DataTable.models.oSettings, {
				"sDestroyWidth": $this[0].style.width,
				"sInstance":     sId,
				"sTableId":      sId
			} );
			oSettings.nTable = this;
			oSettings.oApi   = _that.internal;
			oSettings.oInit  = oInit;
			
			allSettings.push( oSettings );
			
			// Need to add the instance after the instance after the settings object has been added
			// to the settings array, so we can self reference the table instance if more than one
			oSettings.oInstance = (_that.length===1) ? _that : $this.dataTable();
			
			// Backwards compatibility, before we apply all the defaults
			_fnCompatOpts( oInit );
			
			if ( oInit.oLanguage )
			{
				_fnLanguageCompat( oInit.oLanguage );
			}
			
			// If the length menu is given, but the init display length is not, use the length menu
			if ( oInit.aLengthMenu && ! oInit.iDisplayLength )
			{
				oInit.iDisplayLength = $.isArray( oInit.aLengthMenu[0] ) ?
					oInit.aLengthMenu[0][0] : oInit.aLengthMenu[0];
			}
			
			// Apply the defaults and init options to make a single init object will all
			// options defined from defaults and instance options.
			oInit = _fnExtend( $.extend( true, {}, defaults ), oInit );
			
			
			// Map the initialisation options onto the settings object
			_fnMap( oSettings.oFeatures, oInit, [
				"bPaginate",
				"bLengthChange",
				"bFilter",
				"bSort",
				"bSortMulti",
				"bInfo",
				"bProcessing",
				"bAutoWidth",
				"bSortClasses",
				"bServerSide",
				"bDeferRender"
			] );
			_fnMap( oSettings, oInit, [
				"asStripeClasses",
				"ajax",
				"fnServerData",
				"fnFormatNumber",
				"sServerMethod",
				"aaSorting",
				"aaSortingFixed",
				"aLengthMenu",
				"sPaginationType",
				"sAjaxSource",
				"sAjaxDataProp",
				"iStateDuration",
				"sDom",
				"bSortCellsTop",
				"iTabIndex",
				"fnStateLoadCallback",
				"fnStateSaveCallback",
				"renderer",
				"searchDelay",
				"rowId",
				[ "iCookieDuration", "iStateDuration" ], // backwards compat
				[ "oSearch", "oPreviousSearch" ],
				[ "aoSearchCols", "aoPreSearchCols" ],
				[ "iDisplayLength", "_iDisplayLength" ],
				[ "bJQueryUI", "bJUI" ]
			] );
			_fnMap( oSettings.oScroll, oInit, [
				[ "sScrollX", "sX" ],
				[ "sScrollXInner", "sXInner" ],
				[ "sScrollY", "sY" ],
				[ "bScrollCollapse", "bCollapse" ]
			] );
			_fnMap( oSettings.oLanguage, oInit, "fnInfoCallback" );
			
			/* Callback functions which are array driven */
			_fnCallbackReg( oSettings, 'aoDrawCallback',       oInit.fnDrawCallback,      'user' );
			_fnCallbackReg( oSettings, 'aoServerParams',       oInit.fnServerParams,      'user' );
			_fnCallbackReg( oSettings, 'aoStateSaveParams',    oInit.fnStateSaveParams,   'user' );
			_fnCallbackReg( oSettings, 'aoStateLoadParams',    oInit.fnStateLoadParams,   'user' );
			_fnCallbackReg( oSettings, 'aoStateLoaded',        oInit.fnStateLoaded,       'user' );
			_fnCallbackReg( oSettings, 'aoRowCallback',        oInit.fnRowCallback,       'user' );
			_fnCallbackReg( oSettings, 'aoRowCreatedCallback', oInit.fnCreatedRow,        'user' );
			_fnCallbackReg( oSettings, 'aoHeaderCallback',     oInit.fnHeaderCallback,    'user' );
			_fnCallbackReg( oSettings, 'aoFooterCallback',     oInit.fnFooterCallback,    'user' );
			_fnCallbackReg( oSettings, 'aoInitComplete',       oInit.fnInitComplete,      'user' );
			_fnCallbackReg( oSettings, 'aoPreDrawCallback',    oInit.fnPreDrawCallback,   'user' );
			
			oSettings.rowIdFn = _fnGetObjectDataFn( oInit.rowId );
			
			/* Browser support detection */
			_fnBrowserDetect( oSettings );
			
			var oClasses = oSettings.oClasses;
			
			// @todo Remove in 1.11
			if ( oInit.bJQueryUI )
			{
				/* Use the JUI classes object for display. You could clone the oStdClasses object if
				 * you want to have multiple tables with multiple independent classes
				 */
				$.extend( oClasses, DataTable.ext.oJUIClasses, oInit.oClasses );
			
				if ( oInit.sDom === defaults.sDom && defaults.sDom === "lfrtip" )
				{
					/* Set the DOM to use a layout suitable for jQuery UI's theming */
					oSettings.sDom = '<"H"lfr>t<"F"ip>';
				}
			
				if ( ! oSettings.renderer ) {
					oSettings.renderer = 'jqueryui';
				}
				else if ( $.isPlainObject( oSettings.renderer ) && ! oSettings.renderer.header ) {
					oSettings.renderer.header = 'jqueryui';
				}
			}
			else
			{
				$.extend( oClasses, DataTable.ext.classes, oInit.oClasses );
			}
			$this.addClass( oClasses.sTable );
			
			
			if ( oSettings.iInitDisplayStart === undefined )
			{
				/* Display start point, taking into account the save saving */
				oSettings.iInitDisplayStart = oInit.iDisplayStart;
				oSettings._iDisplayStart = oInit.iDisplayStart;
			}
			
			if ( oInit.iDeferLoading !== null )
			{
				oSettings.bDeferLoading = true;
				var tmp = $.isArray( oInit.iDeferLoading );
				oSettings._iRecordsDisplay = tmp ? oInit.iDeferLoading[0] : oInit.iDeferLoading;
				oSettings._iRecordsTotal = tmp ? oInit.iDeferLoading[1] : oInit.iDeferLoading;
			}
			
			/* Language definitions */
			var oLanguage = oSettings.oLanguage;
			$.extend( true, oLanguage, oInit.oLanguage );
			
			if ( oLanguage.sUrl !== "" )
			{
				/* Get the language definitions from a file - because this Ajax call makes the language
				 * get async to the remainder of this function we use bInitHandedOff to indicate that
				 * _fnInitialise will be fired by the returned Ajax handler, rather than the constructor
				 */
				$.ajax( {
					dataType: 'json',
					url: oLanguage.sUrl,
					success: function ( json ) {
						_fnLanguageCompat( json );
						_fnCamelToHungarian( defaults.oLanguage, json );
						$.extend( true, oLanguage, json );
						_fnInitialise( oSettings );
					},
					error: function () {
						// Error occurred loading language file, continue on as best we can
						_fnInitialise( oSettings );
					}
				} );
				bInitHandedOff = true;
			}
			
			/*
			 * Stripes
			 */
			if ( oInit.asStripeClasses === null )
			{
				oSettings.asStripeClasses =[
					oClasses.sStripeOdd,
					oClasses.sStripeEven
				];
			}
			
			/* Remove row stripe classes if they are already on the table row */
			var stripeClasses = oSettings.asStripeClasses;
			var rowOne = $this.children('tbody').find('tr').eq(0);
			if ( $.inArray( true, $.map( stripeClasses, function(el, i) {
				return rowOne.hasClass(el);
			} ) ) !== -1 ) {
				$('tbody tr', this).removeClass( stripeClasses.join(' ') );
				oSettings.asDestroyStripes = stripeClasses.slice();
			}
			
			/*
			 * Columns
			 * See if we should load columns automatically or use defined ones
			 */
			var anThs = [];
			var aoColumnsInit;
			var nThead = this.getElementsByTagName('thead');
			if ( nThead.length !== 0 )
			{
				_fnDetectHeader( oSettings.aoHeader, nThead[0] );
				anThs = _fnGetUniqueThs( oSettings );
			}
			
			/* If not given a column array, generate one with nulls */
			if ( oInit.aoColumns === null )
			{
				aoColumnsInit = [];
				for ( i=0, iLen=anThs.length ; i<iLen ; i++ )
				{
					aoColumnsInit.push( null );
				}
			}
			else
			{
				aoColumnsInit = oInit.aoColumns;
			}
			
			/* Add the columns */
			for ( i=0, iLen=aoColumnsInit.length ; i<iLen ; i++ )
			{
				_fnAddColumn( oSettings, anThs ? anThs[i] : null );
			}
			
			/* Apply the column definitions */
			_fnApplyColumnDefs( oSettings, oInit.aoColumnDefs, aoColumnsInit, function (iCol, oDef) {
				_fnColumnOptions( oSettings, iCol, oDef );
			} );
			
			/* HTML5 attribute detection - build an mData object automatically if the
			 * attributes are found
			 */
			if ( rowOne.length ) {
				var a = function ( cell, name ) {
					return cell.getAttribute( 'data-'+name ) !== null ? name : null;
				};
			
				$( rowOne[0] ).children('th, td').each( function (i, cell) {
					var col = oSettings.aoColumns[i];
			
					if ( col.mData === i ) {
						var sort = a( cell, 'sort' ) || a( cell, 'order' );
						var filter = a( cell, 'filter' ) || a( cell, 'search' );
			
						if ( sort !== null || filter !== null ) {
							col.mData = {
								_:      i+'.display',
								sort:   sort !== null   ? i+'.@data-'+sort   : undefined,
								type:   sort !== null   ? i+'.@data-'+sort   : undefined,
								filter: filter !== null ? i+'.@data-'+filter : undefined
							};
			
							_fnColumnOptions( oSettings, i );
						}
					}
				} );
			}
			
			var features = oSettings.oFeatures;
			
			/* Must be done after everything which can be overridden by the state saving! */
			if ( oInit.bStateSave )
			{
				features.bStateSave = true;
				_fnLoadState( oSettings, oInit );
				_fnCallbackReg( oSettings, 'aoDrawCallback', _fnSaveState, 'state_save' );
			}
			
			
			/*
			 * Sorting
			 * @todo For modularisation (1.11) this needs to do into a sort start up handler
			 */
			
			// If aaSorting is not defined, then we use the first indicator in asSorting
			// in case that has been altered, so the default sort reflects that option
			if ( oInit.aaSorting === undefined )
			{
				var sorting = oSettings.aaSorting;
				for ( i=0, iLen=sorting.length ; i<iLen ; i++ )
				{
					sorting[i][1] = oSettings.aoColumns[ i ].asSorting[0];
				}
			}
			
			/* Do a first pass on the sorting classes (allows any size changes to be taken into
			 * account, and also will apply sorting disabled classes if disabled
			 */
			_fnSortingClasses( oSettings );
			
			if ( features.bSort )
			{
				_fnCallbackReg( oSettings, 'aoDrawCallback', function () {
					if ( oSettings.bSorted ) {
						var aSort = _fnSortFlatten( oSettings );
						var sortedColumns = {};
			
						$.each( aSort, function (i, val) {
							sortedColumns[ val.src ] = val.dir;
						} );
			
						_fnCallbackFire( oSettings, null, 'order', [oSettings, aSort, sortedColumns] );
						_fnSortAria( oSettings );
					}
				} );
			}
			
			_fnCallbackReg( oSettings, 'aoDrawCallback', function () {
				if ( oSettings.bSorted || _fnDataSource( oSettings ) === 'ssp' || features.bDeferRender ) {
					_fnSortingClasses( oSettings );
				}
			}, 'sc' );
			
			
			/*
			 * Final init
			 * Cache the header, body and footer as required, creating them if needed
			 */
			
			// Work around for Webkit bug 83867 - store the caption-side before removing from doc
			var captions = $this.children('caption').each( function () {
				this._captionSide = $this.css('caption-side');
			} );
			
			var thead = $this.children('thead');
			if ( thead.length === 0 )
			{
				thead = $('<thead/>').appendTo(this);
			}
			oSettings.nTHead = thead[0];
			
			var tbody = $this.children('tbody');
			if ( tbody.length === 0 )
			{
				tbody = $('<tbody/>').appendTo(this);
			}
			oSettings.nTBody = tbody[0];
			
			var tfoot = $this.children('tfoot');
			if ( tfoot.length === 0 && captions.length > 0 && (oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "") )
			{
				// If we are a scrolling table, and no footer has been given, then we need to create
				// a tfoot element for the caption element to be appended to
				tfoot = $('<tfoot/>').appendTo(this);
			}
			
			if ( tfoot.length === 0 || tfoot.children().length === 0 ) {
				$this.addClass( oClasses.sNoFooter );
			}
			else if ( tfoot.length > 0 ) {
				oSettings.nTFoot = tfoot[0];
				_fnDetectHeader( oSettings.aoFooter, oSettings.nTFoot );
			}
			
			/* Check if there is data passing into the constructor */
			if ( oInit.aaData )
			{
				for ( i=0 ; i<oInit.aaData.length ; i++ )
				{
					_fnAddData( oSettings, oInit.aaData[ i ] );
				}
			}
			else if ( oSettings.bDeferLoading || _fnDataSource( oSettings ) == 'dom' )
			{
				/* Grab the data from the page - only do this when deferred loading or no Ajax
				 * source since there is no point in reading the DOM data if we are then going
				 * to replace it with Ajax data
				 */
				_fnAddTr( oSettings, $(oSettings.nTBody).children('tr') );
			}
			
			/* Copy the data index array */
			oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
			
			/* Initialisation complete - table can be drawn */
			oSettings.bInitialised = true;
			
			/* Check if we need to initialise the table (it might not have been handed off to the
			 * language processor)
			 */
			if ( bInitHandedOff === false )
			{
				_fnInitialise( oSettings );
			}
		} );
		_that = null;
		return this;
	};

	
	/*
	 * It is useful to have variables which are scoped locally so only the
	 * DataTables functions can access them and they don't leak into global space.
	 * At the same time these functions are often useful over multiple files in the
	 * core and API, so we list, or at least document, all variables which are used
	 * by DataTables as private variables here. This also ensures that there is no
	 * clashing of variable names and that they can easily referenced for reuse.
	 */
	
	
	// Defined else where
	//  _selector_run
	//  _selector_opts
	//  _selector_first
	//  _selector_row_indexes
	
	var _ext; // DataTable.ext
	var _Api; // DataTable.Api
	var _api_register; // DataTable.Api.register
	var _api_registerPlural; // DataTable.Api.registerPlural
	
	var _re_dic = {};
	var _re_new_lines = /[\r\n]/g;
	var _re_html = /<.*?>/g;
	var _re_date_start = /^[\w\+\-]/;
	var _re_date_end = /[\w\+\-]$/;
	
	// Escape regular expression special characters
	var _re_escape_regex = new RegExp( '(\\' + [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ].join('|\\') + ')', 'g' );
	
	// http://en.wikipedia.org/wiki/Foreign_exchange_market
	// - \u20BD - Russian ruble.
	// - \u20a9 - South Korean Won
	// - \u20BA - Turkish Lira
	// - \u20B9 - Indian Rupee
	// - R - Brazil (R$) and South Africa
	// - fr - Swiss Franc
	// - kr - Swedish krona, Norwegian krone and Danish krone
	// - \u2009 is thin space and \u202F is narrow no-break space, both used in many
	//   standards as thousands separators.
	var _re_formatted_numeric = /[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi;
	
	
	var _empty = function ( d ) {
		return !d || d === true || d === '-' ? true : false;
	};
	
	
	var _intVal = function ( s ) {
		var integer = parseInt( s, 10 );
		return !isNaN(integer) && isFinite(s) ? integer : null;
	};
	
	// Convert from a formatted number with characters other than `.` as the
	// decimal place, to a Javascript number
	var _numToDecimal = function ( num, decimalPoint ) {
		// Cache created regular expressions for speed as this function is called often
		if ( ! _re_dic[ decimalPoint ] ) {
			_re_dic[ decimalPoint ] = new RegExp( _fnEscapeRegex( decimalPoint ), 'g' );
		}
		return typeof num === 'string' && decimalPoint !== '.' ?
			num.replace( /\./g, '' ).replace( _re_dic[ decimalPoint ], '.' ) :
			num;
	};
	
	
	var _isNumber = function ( d, decimalPoint, formatted ) {
		var strType = typeof d === 'string';
	
		// If empty return immediately so there must be a number if it is a
		// formatted string (this stops the string "k", or "kr", etc being detected
		// as a formatted number for currency
		if ( _empty( d ) ) {
			return true;
		}
	
		if ( decimalPoint && strType ) {
			d = _numToDecimal( d, decimalPoint );
		}
	
		if ( formatted && strType ) {
			d = d.replace( _re_formatted_numeric, '' );
		}
	
		return !isNaN( parseFloat(d) ) && isFinite( d );
	};
	
	
	// A string without HTML in it can be considered to be HTML still
	var _isHtml = function ( d ) {
		return _empty( d ) || typeof d === 'string';
	};
	
	
	var _htmlNumeric = function ( d, decimalPoint, formatted ) {
		if ( _empty( d ) ) {
			return true;
		}
	
		var html = _isHtml( d );
		return ! html ?
			null :
			_isNumber( _stripHtml( d ), decimalPoint, formatted ) ?
				true :
				null;
	};
	
	
	var _pluck = function ( a, prop, prop2 ) {
		var out = [];
		var i=0, ien=a.length;
	
		// Could have the test in the loop for slightly smaller code, but speed
		// is essential here
		if ( prop2 !== undefined ) {
			for ( ; i<ien ; i++ ) {
				if ( a[i] && a[i][ prop ] ) {
					out.push( a[i][ prop ][ prop2 ] );
				}
			}
		}
		else {
			for ( ; i<ien ; i++ ) {
				if ( a[i] ) {
					out.push( a[i][ prop ] );
				}
			}
		}
	
		return out;
	};
	
	
	// Basically the same as _pluck, but rather than looping over `a` we use `order`
	// as the indexes to pick from `a`
	var _pluck_order = function ( a, order, prop, prop2 )
	{
		var out = [];
		var i=0, ien=order.length;
	
		// Could have the test in the loop for slightly smaller code, but speed
		// is essential here
		if ( prop2 !== undefined ) {
			for ( ; i<ien ; i++ ) {
				if ( a[ order[i] ][ prop ] ) {
					out.push( a[ order[i] ][ prop ][ prop2 ] );
				}
			}
		}
		else {
			for ( ; i<ien ; i++ ) {
				out.push( a[ order[i] ][ prop ] );
			}
		}
	
		return out;
	};
	
	
	var _range = function ( len, start )
	{
		var out = [];
		var end;
	
		if ( start === undefined ) {
			start = 0;
			end = len;
		}
		else {
			end = start;
			start = len;
		}
	
		for ( var i=start ; i<end ; i++ ) {
			out.push( i );
		}
	
		return out;
	};
	
	
	var _removeEmpty = function ( a )
	{
		var out = [];
	
		for ( var i=0, ien=a.length ; i<ien ; i++ ) {
			if ( a[i] ) { // careful - will remove all falsy values!
				out.push( a[i] );
			}
		}
	
		return out;
	};
	
	
	var _stripHtml = function ( d ) {
		return d.replace( _re_html, '' );
	};
	
	
	/**
	 * Find the unique elements in a source array.
	 *
	 * @param  {array} src Source array
	 * @return {array} Array of unique items
	 * @ignore
	 */
	var _unique = function ( src )
	{
		// A faster unique method is to use object keys to identify used values,
		// but this doesn't work with arrays or objects, which we must also
		// consider. See jsperf.com/compare-array-unique-versions/4 for more
		// information.
		var
			out = [],
			val,
			i, ien=src.length,
			j, k=0;
	
		again: for ( i=0 ; i<ien ; i++ ) {
			val = src[i];
	
			for ( j=0 ; j<k ; j++ ) {
				if ( out[j] === val ) {
					continue again;
				}
			}
	
			out.push( val );
			k++;
		}
	
		return out;
	};
	
	
	/**
	 * DataTables utility methods
	 * 
	 * This namespace provides helper methods that DataTables uses internally to
	 * create a DataTable, but which are not exclusively used only for DataTables.
	 * These methods can be used by extension authors to save the duplication of
	 * code.
	 *
	 *  @namespace
	 */
	DataTable.util = {
		/**
		 * Throttle the calls to a function. Arguments and context are maintained
		 * for the throttled function.
		 *
		 * @param {function} fn Function to be called
		 * @param {integer} freq Call frequency in mS
		 * @return {function} Wrapped function
		 */
		throttle: function ( fn, freq ) {
			var
				frequency = freq !== undefined ? freq : 200,
				last,
				timer;
	
			return function () {
				var
					that = this,
					now  = +new Date(),
					args = arguments;
	
				if ( last && now < last + frequency ) {
					clearTimeout( timer );
	
					timer = setTimeout( function () {
						last = undefined;
						fn.apply( that, args );
					}, frequency );
				}
				else {
					last = now;
					fn.apply( that, args );
				}
			};
		},
	
	
		/**
		 * Escape a string such that it can be used in a regular expression
		 *
		 *  @param {string} val string to escape
		 *  @returns {string} escaped string
		 */
		escapeRegex: function ( val ) {
			return val.replace( _re_escape_regex, '\\$1' );
		}
	};
	
	
	
	/**
	 * Create a mapping object that allows camel case parameters to be looked up
	 * for their Hungarian counterparts. The mapping is stored in a private
	 * parameter called `_hungarianMap` which can be accessed on the source object.
	 *  @param {object} o
	 *  @memberof DataTable#oApi
	 */
	function _fnHungarianMap ( o )
	{
		var
			hungarian = 'a aa ai ao as b fn i m o s ',
			match,
			newKey,
			map = {};
	
		$.each( o, function (key, val) {
			match = key.match(/^([^A-Z]+?)([A-Z])/);
	
			if ( match && hungarian.indexOf(match[1]+' ') !== -1 )
			{
				newKey = key.replace( match[0], match[2].toLowerCase() );
				map[ newKey ] = key;
	
				if ( match[1] === 'o' )
				{
					_fnHungarianMap( o[key] );
				}
			}
		} );
	
		o._hungarianMap = map;
	}
	
	
	/**
	 * Convert from camel case parameters to Hungarian, based on a Hungarian map
	 * created by _fnHungarianMap.
	 *  @param {object} src The model object which holds all parameters that can be
	 *    mapped.
	 *  @param {object} user The object to convert from camel case to Hungarian.
	 *  @param {boolean} force When set to `true`, properties which already have a
	 *    Hungarian value in the `user` object will be overwritten. Otherwise they
	 *    won't be.
	 *  @memberof DataTable#oApi
	 */
	function _fnCamelToHungarian ( src, user, force )
	{
		if ( ! src._hungarianMap ) {
			_fnHungarianMap( src );
		}
	
		var hungarianKey;
	
		$.each( user, function (key, val) {
			hungarianKey = src._hungarianMap[ key ];
	
			if ( hungarianKey !== undefined && (force || user[hungarianKey] === undefined) )
			{
				// For objects, we need to buzz down into the object to copy parameters
				if ( hungarianKey.charAt(0) === 'o' )
				{
					// Copy the camelCase options over to the hungarian
					if ( ! user[ hungarianKey ] ) {
						user[ hungarianKey ] = {};
					}
					$.extend( true, user[hungarianKey], user[key] );
	
					_fnCamelToHungarian( src[hungarianKey], user[hungarianKey], force );
				}
				else {
					user[hungarianKey] = user[ key ];
				}
			}
		} );
	}
	
	
	/**
	 * Language compatibility - when certain options are given, and others aren't, we
	 * need to duplicate the values over, in order to provide backwards compatibility
	 * with older language files.
	 *  @param {object} oSettings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnLanguageCompat( lang )
	{
		var defaults = DataTable.defaults.oLanguage;
		var zeroRecords = lang.sZeroRecords;
	
		/* Backwards compatibility - if there is no sEmptyTable given, then use the same as
		 * sZeroRecords - assuming that is given.
		 */
		if ( ! lang.sEmptyTable && zeroRecords &&
			defaults.sEmptyTable === "No data available in table" )
		{
			_fnMap( lang, lang, 'sZeroRecords', 'sEmptyTable' );
		}
	
		/* Likewise with loading records */
		if ( ! lang.sLoadingRecords && zeroRecords &&
			defaults.sLoadingRecords === "Loading..." )
		{
			_fnMap( lang, lang, 'sZeroRecords', 'sLoadingRecords' );
		}
	
		// Old parameter name of the thousands separator mapped onto the new
		if ( lang.sInfoThousands ) {
			lang.sThousands = lang.sInfoThousands;
		}
	
		var decimal = lang.sDecimal;
		if ( decimal ) {
			_addNumericSort( decimal );
		}
	}
	
	
	/**
	 * Map one parameter onto another
	 *  @param {object} o Object to map
	 *  @param {*} knew The new parameter name
	 *  @param {*} old The old parameter name
	 */
	var _fnCompatMap = function ( o, knew, old ) {
		if ( o[ knew ] !== undefined ) {
			o[ old ] = o[ knew ];
		}
	};
	
	
	/**
	 * Provide backwards compatibility for the main DT options. Note that the new
	 * options are mapped onto the old parameters, so this is an external interface
	 * change only.
	 *  @param {object} init Object to map
	 */
	function _fnCompatOpts ( init )
	{
		_fnCompatMap( init, 'ordering',      'bSort' );
		_fnCompatMap( init, 'orderMulti',    'bSortMulti' );
		_fnCompatMap( init, 'orderClasses',  'bSortClasses' );
		_fnCompatMap( init, 'orderCellsTop', 'bSortCellsTop' );
		_fnCompatMap( init, 'order',         'aaSorting' );
		_fnCompatMap( init, 'orderFixed',    'aaSortingFixed' );
		_fnCompatMap( init, 'paging',        'bPaginate' );
		_fnCompatMap( init, 'pagingType',    'sPaginationType' );
		_fnCompatMap( init, 'pageLength',    'iDisplayLength' );
		_fnCompatMap( init, 'searching',     'bFilter' );
	
		// Boolean initialisation of x-scrolling
		if ( typeof init.sScrollX === 'boolean' ) {
			init.sScrollX = init.sScrollX ? '100%' : '';
		}
		if ( typeof init.scrollX === 'boolean' ) {
			init.scrollX = init.scrollX ? '100%' : '';
		}
	
		// Column search objects are in an array, so it needs to be converted
		// element by element
		var searchCols = init.aoSearchCols;
	
		if ( searchCols ) {
			for ( var i=0, ien=searchCols.length ; i<ien ; i++ ) {
				if ( searchCols[i] ) {
					_fnCamelToHungarian( DataTable.models.oSearch, searchCols[i] );
				}
			}
		}
	}
	
	
	/**
	 * Provide backwards compatibility for column options. Note that the new options
	 * are mapped onto the old parameters, so this is an external interface change
	 * only.
	 *  @param {object} init Object to map
	 */
	function _fnCompatCols ( init )
	{
		_fnCompatMap( init, 'orderable',     'bSortable' );
		_fnCompatMap( init, 'orderData',     'aDataSort' );
		_fnCompatMap( init, 'orderSequence', 'asSorting' );
		_fnCompatMap( init, 'orderDataType', 'sortDataType' );
	
		// orderData can be given as an integer
		var dataSort = init.aDataSort;
		if ( dataSort && ! $.isArray( dataSort ) ) {
			init.aDataSort = [ dataSort ];
		}
	}
	
	
	/**
	 * Browser feature detection for capabilities, quirks
	 *  @param {object} settings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnBrowserDetect( settings )
	{
		// We don't need to do this every time DataTables is constructed, the values
		// calculated are specific to the browser and OS configuration which we
		// don't expect to change between initialisations
		if ( ! DataTable.__browser ) {
			var browser = {};
			DataTable.__browser = browser;
	
			// Scrolling feature / quirks detection
			var n = $('<div/>')
				.css( {
					position: 'fixed',
					top: 0,
					left: 0,
					height: 1,
					width: 1,
					overflow: 'hidden'
				} )
				.append(
					$('<div/>')
						.css( {
							position: 'absolute',
							top: 1,
							left: 1,
							width: 100,
							overflow: 'scroll'
						} )
						.append(
							$('<div/>')
								.css( {
									width: '100%',
									height: 10
								} )
						)
				)
				.appendTo( 'body' );
	
			var outer = n.children();
			var inner = outer.children();
	
			// Numbers below, in order, are:
			// inner.offsetWidth, inner.clientWidth, outer.offsetWidth, outer.clientWidth
			//
			// IE6 XP:                           100 100 100  83
			// IE7 Vista:                        100 100 100  83
			// IE 8+ Windows:                     83  83 100  83
			// Evergreen Windows:                 83  83 100  83
			// Evergreen Mac with scrollbars:     85  85 100  85
			// Evergreen Mac without scrollbars: 100 100 100 100
	
			// Get scrollbar width
			browser.barWidth = outer[0].offsetWidth - outer[0].clientWidth;
	
			// IE6/7 will oversize a width 100% element inside a scrolling element, to
			// include the width of the scrollbar, while other browsers ensure the inner
			// element is contained without forcing scrolling
			browser.bScrollOversize = inner[0].offsetWidth === 100 && outer[0].clientWidth !== 100;
	
			// In rtl text layout, some browsers (most, but not all) will place the
			// scrollbar on the left, rather than the right.
			browser.bScrollbarLeft = Math.round( inner.offset().left ) !== 1;
	
			// IE8- don't provide height and width for getBoundingClientRect
			browser.bBounding = n[0].getBoundingClientRect().width ? true : false;
	
			n.remove();
		}
	
		$.extend( settings.oBrowser, DataTable.__browser );
		settings.oScroll.iBarWidth = DataTable.__browser.barWidth;
	}
	
	
	/**
	 * Array.prototype reduce[Right] method, used for browsers which don't support
	 * JS 1.6. Done this way to reduce code size, since we iterate either way
	 *  @param {object} settings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnReduce ( that, fn, init, start, end, inc )
	{
		var
			i = start,
			value,
			isSet = false;
	
		if ( init !== undefined ) {
			value = init;
			isSet = true;
		}
	
		while ( i !== end ) {
			if ( ! that.hasOwnProperty(i) ) {
				continue;
			}
	
			value = isSet ?
				fn( value, that[i], i, that ) :
				that[i];
	
			isSet = true;
			i += inc;
		}
	
		return value;
	}
	
	/**
	 * Add a column to the list used for the table with default values
	 *  @param {object} oSettings dataTables settings object
	 *  @param {node} nTh The th element for this column
	 *  @memberof DataTable#oApi
	 */
	function _fnAddColumn( oSettings, nTh )
	{
		// Add column to aoColumns array
		var oDefaults = DataTable.defaults.column;
		var iCol = oSettings.aoColumns.length;
		var oCol = $.extend( {}, DataTable.models.oColumn, oDefaults, {
			"nTh": nTh ? nTh : document.createElement('th'),
			"sTitle":    oDefaults.sTitle    ? oDefaults.sTitle    : nTh ? nTh.innerHTML : '',
			"aDataSort": oDefaults.aDataSort ? oDefaults.aDataSort : [iCol],
			"mData": oDefaults.mData ? oDefaults.mData : iCol,
			idx: iCol
		} );
		oSettings.aoColumns.push( oCol );
	
		// Add search object for column specific search. Note that the `searchCols[ iCol ]`
		// passed into extend can be undefined. This allows the user to give a default
		// with only some of the parameters defined, and also not give a default
		var searchCols = oSettings.aoPreSearchCols;
		searchCols[ iCol ] = $.extend( {}, DataTable.models.oSearch, searchCols[ iCol ] );
	
		// Use the default column options function to initialise classes etc
		_fnColumnOptions( oSettings, iCol, $(nTh).data() );
	}
	
	
	/**
	 * Apply options for a column
	 *  @param {object} oSettings dataTables settings object
	 *  @param {int} iCol column index to consider
	 *  @param {object} oOptions object with sType, bVisible and bSearchable etc
	 *  @memberof DataTable#oApi
	 */
	function _fnColumnOptions( oSettings, iCol, oOptions )
	{
		var oCol = oSettings.aoColumns[ iCol ];
		var oClasses = oSettings.oClasses;
		var th = $(oCol.nTh);
	
		// Try to get width information from the DOM. We can't get it from CSS
		// as we'd need to parse the CSS stylesheet. `width` option can override
		if ( ! oCol.sWidthOrig ) {
			// Width attribute
			oCol.sWidthOrig = th.attr('width') || null;
	
			// Style attribute
			var t = (th.attr('style') || '').match(/width:\s*(\d+[pxem%]+)/);
			if ( t ) {
				oCol.sWidthOrig = t[1];
			}
		}
	
		/* User specified column options */
		if ( oOptions !== undefined && oOptions !== null )
		{
			// Backwards compatibility
			_fnCompatCols( oOptions );
	
			// Map camel case parameters to their Hungarian counterparts
			_fnCamelToHungarian( DataTable.defaults.column, oOptions );
	
			/* Backwards compatibility for mDataProp */
			if ( oOptions.mDataProp !== undefined && !oOptions.mData )
			{
				oOptions.mData = oOptions.mDataProp;
			}
	
			if ( oOptions.sType )
			{
				oCol._sManualType = oOptions.sType;
			}
	
			// `class` is a reserved word in Javascript, so we need to provide
			// the ability to use a valid name for the camel case input
			if ( oOptions.className && ! oOptions.sClass )
			{
				oOptions.sClass = oOptions.className;
			}
	
			$.extend( oCol, oOptions );
			_fnMap( oCol, oOptions, "sWidth", "sWidthOrig" );
	
			/* iDataSort to be applied (backwards compatibility), but aDataSort will take
			 * priority if defined
			 */
			if ( oOptions.iDataSort !== undefined )
			{
				oCol.aDataSort = [ oOptions.iDataSort ];
			}
			_fnMap( oCol, oOptions, "aDataSort" );
		}
	
		/* Cache the data get and set functions for speed */
		var mDataSrc = oCol.mData;
		var mData = _fnGetObjectDataFn( mDataSrc );
		var mRender = oCol.mRender ? _fnGetObjectDataFn( oCol.mRender ) : null;
	
		var attrTest = function( src ) {
			return typeof src === 'string' && src.indexOf('@') !== -1;
		};
		oCol._bAttrSrc = $.isPlainObject( mDataSrc ) && (
			attrTest(mDataSrc.sort) || attrTest(mDataSrc.type) || attrTest(mDataSrc.filter)
		);
		oCol._setter = null;
	
		oCol.fnGetData = function (rowData, type, meta) {
			var innerData = mData( rowData, type, undefined, meta );
	
			return mRender && type ?
				mRender( innerData, type, rowData, meta ) :
				innerData;
		};
		oCol.fnSetData = function ( rowData, val, meta ) {
			return _fnSetObjectDataFn( mDataSrc )( rowData, val, meta );
		};
	
		// Indicate if DataTables should read DOM data as an object or array
		// Used in _fnGetRowElements
		if ( typeof mDataSrc !== 'number' ) {
			oSettings._rowReadObject = true;
		}
	
		/* Feature sorting overrides column specific when off */
		if ( !oSettings.oFeatures.bSort )
		{
			oCol.bSortable = false;
			th.addClass( oClasses.sSortableNone ); // Have to add class here as order event isn't called
		}
	
		/* Check that the class assignment is correct for sorting */
		var bAsc = $.inArray('asc', oCol.asSorting) !== -1;
		var bDesc = $.inArray('desc', oCol.asSorting) !== -1;
		if ( !oCol.bSortable || (!bAsc && !bDesc) )
		{
			oCol.sSortingClass = oClasses.sSortableNone;
			oCol.sSortingClassJUI = "";
		}
		else if ( bAsc && !bDesc )
		{
			oCol.sSortingClass = oClasses.sSortableAsc;
			oCol.sSortingClassJUI = oClasses.sSortJUIAscAllowed;
		}
		else if ( !bAsc && bDesc )
		{
			oCol.sSortingClass = oClasses.sSortableDesc;
			oCol.sSortingClassJUI = oClasses.sSortJUIDescAllowed;
		}
		else
		{
			oCol.sSortingClass = oClasses.sSortable;
			oCol.sSortingClassJUI = oClasses.sSortJUI;
		}
	}
	
	
	/**
	 * Adjust the table column widths for new data. Note: you would probably want to
	 * do a redraw after calling this function!
	 *  @param {object} settings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnAdjustColumnSizing ( settings )
	{
		/* Not interested in doing column width calculation if auto-width is disabled */
		if ( settings.oFeatures.bAutoWidth !== false )
		{
			var columns = settings.aoColumns;
	
			_fnCalculateColumnWidths( settings );
			for ( var i=0 , iLen=columns.length ; i<iLen ; i++ )
			{
				columns[i].nTh.style.width = columns[i].sWidth;
			}
		}
	
		var scroll = settings.oScroll;
		if ( scroll.sY !== '' || scroll.sX !== '')
		{
			_fnScrollDraw( settings );
		}
	
		_fnCallbackFire( settings, null, 'column-sizing', [settings] );
	}
	
	
	/**
	 * Covert the index of a visible column to the index in the data array (take account
	 * of hidden columns)
	 *  @param {object} oSettings dataTables settings object
	 *  @param {int} iMatch Visible column index to lookup
	 *  @returns {int} i the data index
	 *  @memberof DataTable#oApi
	 */
	function _fnVisibleToColumnIndex( oSettings, iMatch )
	{
		var aiVis = _fnGetColumns( oSettings, 'bVisible' );
	
		return typeof aiVis[iMatch] === 'number' ?
			aiVis[iMatch] :
			null;
	}
	
	
	/**
	 * Covert the index of an index in the data array and convert it to the visible
	 *   column index (take account of hidden columns)
	 *  @param {int} iMatch Column index to lookup
	 *  @param {object} oSettings dataTables settings object
	 *  @returns {int} i the data index
	 *  @memberof DataTable#oApi
	 */
	function _fnColumnIndexToVisible( oSettings, iMatch )
	{
		var aiVis = _fnGetColumns( oSettings, 'bVisible' );
		var iPos = $.inArray( iMatch, aiVis );
	
		return iPos !== -1 ? iPos : null;
	}
	
	
	/**
	 * Get the number of visible columns
	 *  @param {object} oSettings dataTables settings object
	 *  @returns {int} i the number of visible columns
	 *  @memberof DataTable#oApi
	 */
	function _fnVisbleColumns( oSettings )
	{
		var vis = 0;
	
		// No reduce in IE8, use a loop for now
		$.each( oSettings.aoColumns, function ( i, col ) {
			if ( col.bVisible && $(col.nTh).css('display') !== 'none' ) {
				vis++;
			}
		} );
	
		return vis;
	}
	
	
	/**
	 * Get an array of column indexes that match a given property
	 *  @param {object} oSettings dataTables settings object
	 *  @param {string} sParam Parameter in aoColumns to look for - typically
	 *    bVisible or bSearchable
	 *  @returns {array} Array of indexes with matched properties
	 *  @memberof DataTable#oApi
	 */
	function _fnGetColumns( oSettings, sParam )
	{
		var a = [];
	
		$.map( oSettings.aoColumns, function(val, i) {
			if ( val[sParam] ) {
				a.push( i );
			}
		} );
	
		return a;
	}
	
	
	/**
	 * Calculate the 'type' of a column
	 *  @param {object} settings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnColumnTypes ( settings )
	{
		var columns = settings.aoColumns;
		var data = settings.aoData;
		var types = DataTable.ext.type.detect;
		var i, ien, j, jen, k, ken;
		var col, cell, detectedType, cache;
	
		// For each column, spin over the 
		for ( i=0, ien=columns.length ; i<ien ; i++ ) {
			col = columns[i];
			cache = [];
	
			if ( ! col.sType && col._sManualType ) {
				col.sType = col._sManualType;
			}
			else if ( ! col.sType ) {
				for ( j=0, jen=types.length ; j<jen ; j++ ) {
					for ( k=0, ken=data.length ; k<ken ; k++ ) {
						// Use a cache array so we only need to get the type data
						// from the formatter once (when using multiple detectors)
						if ( cache[k] === undefined ) {
							cache[k] = _fnGetCellData( settings, k, i, 'type' );
						}
	
						detectedType = types[j]( cache[k], settings );
	
						// If null, then this type can't apply to this column, so
						// rather than testing all cells, break out. There is an
						// exception for the last type which is `html`. We need to
						// scan all rows since it is possible to mix string and HTML
						// types
						if ( ! detectedType && j !== types.length-1 ) {
							break;
						}
	
						// Only a single match is needed for html type since it is
						// bottom of the pile and very similar to string
						if ( detectedType === 'html' ) {
							break;
						}
					}
	
					// Type is valid for all data points in the column - use this
					// type
					if ( detectedType ) {
						col.sType = detectedType;
						break;
					}
				}
	
				// Fall back - if no type was detected, always use string
				if ( ! col.sType ) {
					col.sType = 'string';
				}
			}
		}
	}
	
	
	/**
	 * Take the column definitions and static columns arrays and calculate how
	 * they relate to column indexes. The callback function will then apply the
	 * definition found for a column to a suitable configuration object.
	 *  @param {object} oSettings dataTables settings object
	 *  @param {array} aoColDefs The aoColumnDefs array that is to be applied
	 *  @param {array} aoCols The aoColumns array that defines columns individually
	 *  @param {function} fn Callback function - takes two parameters, the calculated
	 *    column index and the definition for that column.
	 *  @memberof DataTable#oApi
	 */
	function _fnApplyColumnDefs( oSettings, aoColDefs, aoCols, fn )
	{
		var i, iLen, j, jLen, k, kLen, def;
		var columns = oSettings.aoColumns;
	
		// Column definitions with aTargets
		if ( aoColDefs )
		{
			/* Loop over the definitions array - loop in reverse so first instance has priority */
			for ( i=aoColDefs.length-1 ; i>=0 ; i-- )
			{
				def = aoColDefs[i];
	
				/* Each definition can target multiple columns, as it is an array */
				var aTargets = def.targets !== undefined ?
					def.targets :
					def.aTargets;
	
				if ( ! $.isArray( aTargets ) )
				{
					aTargets = [ aTargets ];
				}
	
				for ( j=0, jLen=aTargets.length ; j<jLen ; j++ )
				{
					if ( typeof aTargets[j] === 'number' && aTargets[j] >= 0 )
					{
						/* Add columns that we don't yet know about */
						while( columns.length <= aTargets[j] )
						{
							_fnAddColumn( oSettings );
						}
	
						/* Integer, basic index */
						fn( aTargets[j], def );
					}
					else if ( typeof aTargets[j] === 'number' && aTargets[j] < 0 )
					{
						/* Negative integer, right to left column counting */
						fn( columns.length+aTargets[j], def );
					}
					else if ( typeof aTargets[j] === 'string' )
					{
						/* Class name matching on TH element */
						for ( k=0, kLen=columns.length ; k<kLen ; k++ )
						{
							if ( aTargets[j] == "_all" ||
							     $(columns[k].nTh).hasClass( aTargets[j] ) )
							{
								fn( k, def );
							}
						}
					}
				}
			}
		}
	
		// Statically defined columns array
		if ( aoCols )
		{
			for ( i=0, iLen=aoCols.length ; i<iLen ; i++ )
			{
				fn( i, aoCols[i] );
			}
		}
	}
	
	/**
	 * Add a data array to the table, creating DOM node etc. This is the parallel to
	 * _fnGatherData, but for adding rows from a Javascript source, rather than a
	 * DOM source.
	 *  @param {object} oSettings dataTables settings object
	 *  @param {array} aData data array to be added
	 *  @param {node} [nTr] TR element to add to the table - optional. If not given,
	 *    DataTables will create a row automatically
	 *  @param {array} [anTds] Array of TD|TH elements for the row - must be given
	 *    if nTr is.
	 *  @returns {int} >=0 if successful (index of new aoData entry), -1 if failed
	 *  @memberof DataTable#oApi
	 */
	function _fnAddData ( oSettings, aDataIn, nTr, anTds )
	{
		/* Create the object for storing information about this new row */
		var iRow = oSettings.aoData.length;
		var oData = $.extend( true, {}, DataTable.models.oRow, {
			src: nTr ? 'dom' : 'data',
			idx: iRow
		} );
	
		oData._aData = aDataIn;
		oSettings.aoData.push( oData );
	
		/* Create the cells */
		var nTd, sThisType;
		var columns = oSettings.aoColumns;
	
		// Invalidate the column types as the new data needs to be revalidated
		for ( var i=0, iLen=columns.length ; i<iLen ; i++ )
		{
			columns[i].sType = null;
		}
	
		/* Add to the display array */
		oSettings.aiDisplayMaster.push( iRow );
	
		var id = oSettings.rowIdFn( aDataIn );
		if ( id !== undefined ) {
			oSettings.aIds[ id ] = oData;
		}
	
		/* Create the DOM information, or register it if already present */
		if ( nTr || ! oSettings.oFeatures.bDeferRender )
		{
			_fnCreateTr( oSettings, iRow, nTr, anTds );
		}
	
		return iRow;
	}
	
	
	/**
	 * Add one or more TR elements to the table. Generally we'd expect to
	 * use this for reading data from a DOM sourced table, but it could be
	 * used for an TR element. Note that if a TR is given, it is used (i.e.
	 * it is not cloned).
	 *  @param {object} settings dataTables settings object
	 *  @param {array|node|jQuery} trs The TR element(s) to add to the table
	 *  @returns {array} Array of indexes for the added rows
	 *  @memberof DataTable#oApi
	 */
	function _fnAddTr( settings, trs )
	{
		var row;
	
		// Allow an individual node to be passed in
		if ( ! (trs instanceof $) ) {
			trs = $(trs);
		}
	
		return trs.map( function (i, el) {
			row = _fnGetRowElements( settings, el );
			return _fnAddData( settings, row.data, el, row.cells );
		} );
	}
	
	
	/**
	 * Take a TR element and convert it to an index in aoData
	 *  @param {object} oSettings dataTables settings object
	 *  @param {node} n the TR element to find
	 *  @returns {int} index if the node is found, null if not
	 *  @memberof DataTable#oApi
	 */
	function _fnNodeToDataIndex( oSettings, n )
	{
		return (n._DT_RowIndex!==undefined) ? n._DT_RowIndex : null;
	}
	
	
	/**
	 * Take a TD element and convert it into a column data index (not the visible index)
	 *  @param {object} oSettings dataTables settings object
	 *  @param {int} iRow The row number the TD/TH can be found in
	 *  @param {node} n The TD/TH element to find
	 *  @returns {int} index if the node is found, -1 if not
	 *  @memberof DataTable#oApi
	 */
	function _fnNodeToColumnIndex( oSettings, iRow, n )
	{
		return $.inArray( n, oSettings.aoData[ iRow ].anCells );
	}
	
	
	/**
	 * Get the data for a given cell from the internal cache, taking into account data mapping
	 *  @param {object} settings dataTables settings object
	 *  @param {int} rowIdx aoData row id
	 *  @param {int} colIdx Column index
	 *  @param {string} type data get type ('display', 'type' 'filter' 'sort')
	 *  @returns {*} Cell data
	 *  @memberof DataTable#oApi
	 */
	function _fnGetCellData( settings, rowIdx, colIdx, type )
	{
		var draw           = settings.iDraw;
		var col            = settings.aoColumns[colIdx];
		var rowData        = settings.aoData[rowIdx]._aData;
		var defaultContent = col.sDefaultContent;
		var cellData       = col.fnGetData( rowData, type, {
			settings: settings,
			row:      rowIdx,
			col:      colIdx
		} );
	
		if ( cellData === undefined ) {
			if ( settings.iDrawError != draw && defaultContent === null ) {
				_fnLog( settings, 0, "Requested unknown parameter "+
					(typeof col.mData=='function' ? '{function}' : "'"+col.mData+"'")+
					" for row "+rowIdx+", column "+colIdx, 4 );
				settings.iDrawError = draw;
			}
			return defaultContent;
		}
	
		// When the data source is null and a specific data type is requested (i.e.
		// not the original data), we can use default column data
		if ( (cellData === rowData || cellData === null) && defaultContent !== null && type !== undefined ) {
			cellData = defaultContent;
		}
		else if ( typeof cellData === 'function' ) {
			// If the data source is a function, then we run it and use the return,
			// executing in the scope of the data object (for instances)
			return cellData.call( rowData );
		}
	
		if ( cellData === null && type == 'display' ) {
			return '';
		}
		return cellData;
	}
	
	
	/**
	 * Set the value for a specific cell, into the internal data cache
	 *  @param {object} settings dataTables settings object
	 *  @param {int} rowIdx aoData row id
	 *  @param {int} colIdx Column index
	 *  @param {*} val Value to set
	 *  @memberof DataTable#oApi
	 */
	function _fnSetCellData( settings, rowIdx, colIdx, val )
	{
		var col     = settings.aoColumns[colIdx];
		var rowData = settings.aoData[rowIdx]._aData;
	
		col.fnSetData( rowData, val, {
			settings: settings,
			row:      rowIdx,
			col:      colIdx
		}  );
	}
	
	
	// Private variable that is used to match action syntax in the data property object
	var __reArray = /\[.*?\]$/;
	var __reFn = /\(\)$/;
	
	/**
	 * Split string on periods, taking into account escaped periods
	 * @param  {string} str String to split
	 * @return {array} Split string
	 */
	function _fnSplitObjNotation( str )
	{
		return $.map( str.match(/(\\.|[^\.])+/g) || [''], function ( s ) {
			return s.replace(/\\./g, '.');
		} );
	}
	
	
	/**
	 * Return a function that can be used to get data from a source object, taking
	 * into account the ability to use nested objects as a source
	 *  @param {string|int|function} mSource The data source for the object
	 *  @returns {function} Data get function
	 *  @memberof DataTable#oApi
	 */
	function _fnGetObjectDataFn( mSource )
	{
		if ( $.isPlainObject( mSource ) )
		{
			/* Build an object of get functions, and wrap them in a single call */
			var o = {};
			$.each( mSource, function (key, val) {
				if ( val ) {
					o[key] = _fnGetObjectDataFn( val );
				}
			} );
	
			return function (data, type, row, meta) {
				var t = o[type] || o._;
				return t !== undefined ?
					t(data, type, row, meta) :
					data;
			};
		}
		else if ( mSource === null )
		{
			/* Give an empty string for rendering / sorting etc */
			return function (data) { // type, row and meta also passed, but not used
				return data;
			};
		}
		else if ( typeof mSource === 'function' )
		{
			return function (data, type, row, meta) {
				return mSource( data, type, row, meta );
			};
		}
		else if ( typeof mSource === 'string' && (mSource.indexOf('.') !== -1 ||
			      mSource.indexOf('[') !== -1 || mSource.indexOf('(') !== -1) )
		{
			/* If there is a . in the source string then the data source is in a
			 * nested object so we loop over the data for each level to get the next
			 * level down. On each loop we test for undefined, and if found immediately
			 * return. This allows entire objects to be missing and sDefaultContent to
			 * be used if defined, rather than throwing an error
			 */
			var fetchData = function (data, type, src) {
				var arrayNotation, funcNotation, out, innerSrc;
	
				if ( src !== "" )
				{
					var a = _fnSplitObjNotation( src );
	
					for ( var i=0, iLen=a.length ; i<iLen ; i++ )
					{
						// Check if we are dealing with special notation
						arrayNotation = a[i].match(__reArray);
						funcNotation = a[i].match(__reFn);
	
						if ( arrayNotation )
						{
							// Array notation
							a[i] = a[i].replace(__reArray, '');
	
							// Condition allows simply [] to be passed in
							if ( a[i] !== "" ) {
								data = data[ a[i] ];
							}
							out = [];
	
							// Get the remainder of the nested object to get
							a.splice( 0, i+1 );
							innerSrc = a.join('.');
	
							// Traverse each entry in the array getting the properties requested
							if ( $.isArray( data ) ) {
								for ( var j=0, jLen=data.length ; j<jLen ; j++ ) {
									out.push( fetchData( data[j], type, innerSrc ) );
								}
							}
	
							// If a string is given in between the array notation indicators, that
							// is used to join the strings together, otherwise an array is returned
							var join = arrayNotation[0].substring(1, arrayNotation[0].length-1);
							data = (join==="") ? out : out.join(join);
	
							// The inner call to fetchData has already traversed through the remainder
							// of the source requested, so we exit from the loop
							break;
						}
						else if ( funcNotation )
						{
							// Function call
							a[i] = a[i].replace(__reFn, '');
							data = data[ a[i] ]();
							continue;
						}
	
						if ( data === null || data[ a[i] ] === undefined )
						{
							return undefined;
						}
						data = data[ a[i] ];
					}
				}
	
				return data;
			};
	
			return function (data, type) { // row and meta also passed, but not used
				return fetchData( data, type, mSource );
			};
		}
		else
		{
			/* Array or flat object mapping */
			return function (data, type) { // row and meta also passed, but not used
				return data[mSource];
			};
		}
	}
	
	
	/**
	 * Return a function that can be used to set data from a source object, taking
	 * into account the ability to use nested objects as a source
	 *  @param {string|int|function} mSource The data source for the object
	 *  @returns {function} Data set function
	 *  @memberof DataTable#oApi
	 */
	function _fnSetObjectDataFn( mSource )
	{
		if ( $.isPlainObject( mSource ) )
		{
			/* Unlike get, only the underscore (global) option is used for for
			 * setting data since we don't know the type here. This is why an object
			 * option is not documented for `mData` (which is read/write), but it is
			 * for `mRender` which is read only.
			 */
			return _fnSetObjectDataFn( mSource._ );
		}
		else if ( mSource === null )
		{
			/* Nothing to do when the data source is null */
			return function () {};
		}
		else if ( typeof mSource === 'function' )
		{
			return function (data, val, meta) {
				mSource( data, 'set', val, meta );
			};
		}
		else if ( typeof mSource === 'string' && (mSource.indexOf('.') !== -1 ||
			      mSource.indexOf('[') !== -1 || mSource.indexOf('(') !== -1) )
		{
			/* Like the get, we need to get data from a nested object */
			var setData = function (data, val, src) {
				var a = _fnSplitObjNotation( src ), b;
				var aLast = a[a.length-1];
				var arrayNotation, funcNotation, o, innerSrc;
	
				for ( var i=0, iLen=a.length-1 ; i<iLen ; i++ )
				{
					// Check if we are dealing with an array notation request
					arrayNotation = a[i].match(__reArray);
					funcNotation = a[i].match(__reFn);
	
					if ( arrayNotation )
					{
						a[i] = a[i].replace(__reArray, '');
						data[ a[i] ] = [];
	
						// Get the remainder of the nested object to set so we can recurse
						b = a.slice();
						b.splice( 0, i+1 );
						innerSrc = b.join('.');
	
						// Traverse each entry in the array setting the properties requested
						if ( $.isArray( val ) )
						{
							for ( var j=0, jLen=val.length ; j<jLen ; j++ )
							{
								o = {};
								setData( o, val[j], innerSrc );
								data[ a[i] ].push( o );
							}
						}
						else
						{
							// We've been asked to save data to an array, but it
							// isn't array data to be saved. Best that can be done
							// is to just save the value.
							data[ a[i] ] = val;
						}
	
						// The inner call to setData has already traversed through the remainder
						// of the source and has set the data, thus we can exit here
						return;
					}
					else if ( funcNotation )
					{
						// Function call
						a[i] = a[i].replace(__reFn, '');
						data = data[ a[i] ]( val );
					}
	
					// If the nested object doesn't currently exist - since we are
					// trying to set the value - create it
					if ( data[ a[i] ] === null || data[ a[i] ] === undefined )
					{
						data[ a[i] ] = {};
					}
					data = data[ a[i] ];
				}
	
				// Last item in the input - i.e, the actual set
				if ( aLast.match(__reFn ) )
				{
					// Function call
					data = data[ aLast.replace(__reFn, '') ]( val );
				}
				else
				{
					// If array notation is used, we just want to strip it and use the property name
					// and assign the value. If it isn't used, then we get the result we want anyway
					data[ aLast.replace(__reArray, '') ] = val;
				}
			};
	
			return function (data, val) { // meta is also passed in, but not used
				return setData( data, val, mSource );
			};
		}
		else
		{
			/* Array or flat object mapping */
			return function (data, val) { // meta is also passed in, but not used
				data[mSource] = val;
			};
		}
	}
	
	
	/**
	 * Return an array with the full table data
	 *  @param {object} oSettings dataTables settings object
	 *  @returns array {array} aData Master data array
	 *  @memberof DataTable#oApi
	 */
	function _fnGetDataMaster ( settings )
	{
		return _pluck( settings.aoData, '_aData' );
	}
	
	
	/**
	 * Nuke the table
	 *  @param {object} oSettings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnClearTable( settings )
	{
		settings.aoData.length = 0;
		settings.aiDisplayMaster.length = 0;
		settings.aiDisplay.length = 0;
		settings.aIds = {};
	}
	
	
	 /**
	 * Take an array of integers (index array) and remove a target integer (value - not
	 * the key!)
	 *  @param {array} a Index array to target
	 *  @param {int} iTarget value to find
	 *  @memberof DataTable#oApi
	 */
	function _fnDeleteIndex( a, iTarget, splice )
	{
		var iTargetIndex = -1;
	
		for ( var i=0, iLen=a.length ; i<iLen ; i++ )
		{
			if ( a[i] == iTarget )
			{
				iTargetIndex = i;
			}
			else if ( a[i] > iTarget )
			{
				a[i]--;
			}
		}
	
		if ( iTargetIndex != -1 && splice === undefined )
		{
			a.splice( iTargetIndex, 1 );
		}
	}
	
	
	/**
	 * Mark cached data as invalid such that a re-read of the data will occur when
	 * the cached data is next requested. Also update from the data source object.
	 *
	 * @param {object} settings DataTables settings object
	 * @param {int}    rowIdx   Row index to invalidate
	 * @param {string} [src]    Source to invalidate from: undefined, 'auto', 'dom'
	 *     or 'data'
	 * @param {int}    [colIdx] Column index to invalidate. If undefined the whole
	 *     row will be invalidated
	 * @memberof DataTable#oApi
	 *
	 * @todo For the modularisation of v1.11 this will need to become a callback, so
	 *   the sort and filter methods can subscribe to it. That will required
	 *   initialisation options for sorting, which is why it is not already baked in
	 */
	function _fnInvalidate( settings, rowIdx, src, colIdx )
	{
		var row = settings.aoData[ rowIdx ];
		var i, ien;
		var cellWrite = function ( cell, col ) {
			// This is very frustrating, but in IE if you just write directly
			// to innerHTML, and elements that are overwritten are GC'ed,
			// even if there is a reference to them elsewhere
			while ( cell.childNodes.length ) {
				cell.removeChild( cell.firstChild );
			}
	
			cell.innerHTML = _fnGetCellData( settings, rowIdx, col, 'display' );
		};
	
		// Are we reading last data from DOM or the data object?
		if ( src === 'dom' || ((! src || src === 'auto') && row.src === 'dom') ) {
			// Read the data from the DOM
			row._aData = _fnGetRowElements(
					settings, row, colIdx, colIdx === undefined ? undefined : row._aData
				)
				.data;
		}
		else {
			// Reading from data object, update the DOM
			var cells = row.anCells;
	
			if ( cells ) {
				if ( colIdx !== undefined ) {
					cellWrite( cells[colIdx], colIdx );
				}
				else {
					for ( i=0, ien=cells.length ; i<ien ; i++ ) {
						cellWrite( cells[i], i );
					}
				}
			}
		}
	
		// For both row and cell invalidation, the cached data for sorting and
		// filtering is nulled out
		row._aSortData = null;
		row._aFilterData = null;
	
		// Invalidate the type for a specific column (if given) or all columns since
		// the data might have changed
		var cols = settings.aoColumns;
		if ( colIdx !== undefined ) {
			cols[ colIdx ].sType = null;
		}
		else {
			for ( i=0, ien=cols.length ; i<ien ; i++ ) {
				cols[i].sType = null;
			}
	
			// Update DataTables special `DT_*` attributes for the row
			_fnRowAttributes( settings, row );
		}
	}
	
	
	/**
	 * Build a data source object from an HTML row, reading the contents of the
	 * cells that are in the row.
	 *
	 * @param {object} settings DataTables settings object
	 * @param {node|object} TR element from which to read data or existing row
	 *   object from which to re-read the data from the cells
	 * @param {int} [colIdx] Optional column index
	 * @param {array|object} [d] Data source object. If `colIdx` is given then this
	 *   parameter should also be given and will be used to write the data into.
	 *   Only the column in question will be written
	 * @returns {object} Object with two parameters: `data` the data read, in
	 *   document order, and `cells` and array of nodes (they can be useful to the
	 *   caller, so rather than needing a second traversal to get them, just return
	 *   them from here).
	 * @memberof DataTable#oApi
	 */
	function _fnGetRowElements( settings, row, colIdx, d )
	{
		var
			tds = [],
			td = row.firstChild,
			name, col, o, i=0, contents,
			columns = settings.aoColumns,
			objectRead = settings._rowReadObject;
	
		// Allow the data object to be passed in, or construct
		d = d !== undefined ?
			d :
			objectRead ?
				{} :
				[];
	
		var attr = function ( str, td  ) {
			if ( typeof str === 'string' ) {
				var idx = str.indexOf('@');
	
				if ( idx !== -1 ) {
					var attr = str.substring( idx+1 );
					var setter = _fnSetObjectDataFn( str );
					setter( d, td.getAttribute( attr ) );
				}
			}
		};
	
		// Read data from a cell and store into the data object
		var cellProcess = function ( cell ) {
			if ( colIdx === undefined || colIdx === i ) {
				col = columns[i];
				contents = $.trim(cell.innerHTML);
	
				if ( col && col._bAttrSrc ) {
					var setter = _fnSetObjectDataFn( col.mData._ );
					setter( d, contents );
	
					attr( col.mData.sort, cell );
					attr( col.mData.type, cell );
					attr( col.mData.filter, cell );
				}
				else {
					// Depending on the `data` option for the columns the data can
					// be read to either an object or an array.
					if ( objectRead ) {
						if ( ! col._setter ) {
							// Cache the setter function
							col._setter = _fnSetObjectDataFn( col.mData );
						}
						col._setter( d, contents );
					}
					else {
						d[i] = contents;
					}
				}
			}
	
			i++;
		};
	
		if ( td ) {
			// `tr` element was passed in
			while ( td ) {
				name = td.nodeName.toUpperCase();
	
				if ( name == "TD" || name == "TH" ) {
					cellProcess( td );
					tds.push( td );
				}
	
				td = td.nextSibling;
			}
		}
		else {
			// Existing row object passed in
			tds = row.anCells;
	
			for ( var j=0, jen=tds.length ; j<jen ; j++ ) {
				cellProcess( tds[j] );
			}
		}
	
		// Read the ID from the DOM if present
		var rowNode = row.firstChild ? row : row.nTr;
	
		if ( rowNode ) {
			var id = rowNode.getAttribute( 'id' );
	
			if ( id ) {
				_fnSetObjectDataFn( settings.rowId )( d, id );
			}
		}
	
		return {
			data: d,
			cells: tds
		};
	}
	/**
	 * Create a new TR element (and it's TD children) for a row
	 *  @param {object} oSettings dataTables settings object
	 *  @param {int} iRow Row to consider
	 *  @param {node} [nTrIn] TR element to add to the table - optional. If not given,
	 *    DataTables will create a row automatically
	 *  @param {array} [anTds] Array of TD|TH elements for the row - must be given
	 *    if nTr is.
	 *  @memberof DataTable#oApi
	 */
	function _fnCreateTr ( oSettings, iRow, nTrIn, anTds )
	{
		var
			row = oSettings.aoData[iRow],
			rowData = row._aData,
			cells = [],
			nTr, nTd, oCol,
			i, iLen;
	
		if ( row.nTr === null )
		{
			nTr = nTrIn || document.createElement('tr');
	
			row.nTr = nTr;
			row.anCells = cells;
	
			/* Use a private property on the node to allow reserve mapping from the node
			 * to the aoData array for fast look up
			 */
			nTr._DT_RowIndex = iRow;
	
			/* Special parameters can be given by the data source to be used on the row */
			_fnRowAttributes( oSettings, row );
	
			/* Process each column */
			for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
			{
				oCol = oSettings.aoColumns[i];
	
				nTd = nTrIn ? anTds[i] : document.createElement( oCol.sCellType );
				nTd._DT_CellIndex = {
					row: iRow,
					column: i
				};
				
				cells.push( nTd );
	
				// Need to create the HTML if new, or if a rendering function is defined
				if ( (!nTrIn || oCol.mRender || oCol.mData !== i) &&
					 (!$.isPlainObject(oCol.mData) || oCol.mData._ !== i+'.display')
				) {
					nTd.innerHTML = _fnGetCellData( oSettings, iRow, i, 'display' );
				}
	
				/* Add user defined class */
				if ( oCol.sClass )
				{
					nTd.className += ' '+oCol.sClass;
				}
	
				// Visibility - add or remove as required
				if ( oCol.bVisible && ! nTrIn )
				{
					nTr.appendChild( nTd );
				}
				else if ( ! oCol.bVisible && nTrIn )
				{
					nTd.parentNode.removeChild( nTd );
				}
	
				if ( oCol.fnCreatedCell )
				{
					oCol.fnCreatedCell.call( oSettings.oInstance,
						nTd, _fnGetCellData( oSettings, iRow, i ), rowData, iRow, i
					);
				}
			}
	
			_fnCallbackFire( oSettings, 'aoRowCreatedCallback', null, [nTr, rowData, iRow] );
		}
	
		// Remove once webkit bug 131819 and Chromium bug 365619 have been resolved
		// and deployed
		row.nTr.setAttribute( 'role', 'row' );
	}
	
	
	/**
	 * Add attributes to a row based on the special `DT_*` parameters in a data
	 * source object.
	 *  @param {object} settings DataTables settings object
	 *  @param {object} DataTables row object for the row to be modified
	 *  @memberof DataTable#oApi
	 */
	function _fnRowAttributes( settings, row )
	{
		var tr = row.nTr;
		var data = row._aData;
	
		if ( tr ) {
			var id = settings.rowIdFn( data );
	
			if ( id ) {
				tr.id = id;
			}
	
			if ( data.DT_RowClass ) {
				// Remove any classes added by DT_RowClass before
				var a = data.DT_RowClass.split(' ');
				row.__rowc = row.__rowc ?
					_unique( row.__rowc.concat( a ) ) :
					a;
	
				$(tr)
					.removeClass( row.__rowc.join(' ') )
					.addClass( data.DT_RowClass );
			}
	
			if ( data.DT_RowAttr ) {
				$(tr).attr( data.DT_RowAttr );
			}
	
			if ( data.DT_RowData ) {
				$(tr).data( data.DT_RowData );
			}
		}
	}
	
	
	/**
	 * Create the HTML header for the table
	 *  @param {object} oSettings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnBuildHead( oSettings )
	{
		var i, ien, cell, row, column;
		var thead = oSettings.nTHead;
		var tfoot = oSettings.nTFoot;
		var createHeader = $('th, td', thead).length === 0;
		var classes = oSettings.oClasses;
		var columns = oSettings.aoColumns;
	
		if ( createHeader ) {
			row = $('<tr/>').appendTo( thead );
		}
	
		for ( i=0, ien=columns.length ; i<ien ; i++ ) {
			column = columns[i];
			cell = $( column.nTh ).addClass( column.sClass );
	
			if ( createHeader ) {
				cell.appendTo( row );
			}
	
			// 1.11 move into sorting
			if ( oSettings.oFeatures.bSort ) {
				cell.addClass( column.sSortingClass );
	
				if ( column.bSortable !== false ) {
					cell
						.attr( 'tabindex', oSettings.iTabIndex )
						.attr( 'aria-controls', oSettings.sTableId );
	
					_fnSortAttachListener( oSettings, column.nTh, i );
				}
			}
	
			if ( column.sTitle != cell[0].innerHTML ) {
				cell.html( column.sTitle );
			}
	
			_fnRenderer( oSettings, 'header' )(
				oSettings, cell, column, classes
			);
		}
	
		if ( createHeader ) {
			_fnDetectHeader( oSettings.aoHeader, thead );
		}
		
		/* ARIA role for the rows */
	 	$(thead).find('>tr').attr('role', 'row');
	
		/* Deal with the footer - add classes if required */
		$(thead).find('>tr>th, >tr>td').addClass( classes.sHeaderTH );
		$(tfoot).find('>tr>th, >tr>td').addClass( classes.sFooterTH );
	
		// Cache the footer cells. Note that we only take the cells from the first
		// row in the footer. If there is more than one row the user wants to
		// interact with, they need to use the table().foot() method. Note also this
		// allows cells to be used for multiple columns using colspan
		if ( tfoot !== null ) {
			var cells = oSettings.aoFooter[0];
	
			for ( i=0, ien=cells.length ; i<ien ; i++ ) {
				column = columns[i];
				column.nTf = cells[i].cell;
	
				if ( column.sClass ) {
					$(column.nTf).addClass( column.sClass );
				}
			}
		}
	}
	
	
	/**
	 * Draw the header (or footer) element based on the column visibility states. The
	 * methodology here is to use the layout array from _fnDetectHeader, modified for
	 * the instantaneous column visibility, to construct the new layout. The grid is
	 * traversed over cell at a time in a rows x columns grid fashion, although each
	 * cell insert can cover multiple elements in the grid - which is tracks using the
	 * aApplied array. Cell inserts in the grid will only occur where there isn't
	 * already a cell in that position.
	 *  @param {object} oSettings dataTables settings object
	 *  @param array {objects} aoSource Layout array from _fnDetectHeader
	 *  @param {boolean} [bIncludeHidden=false] If true then include the hidden columns in the calc,
	 *  @memberof DataTable#oApi
	 */
	function _fnDrawHead( oSettings, aoSource, bIncludeHidden )
	{
		var i, iLen, j, jLen, k, kLen, n, nLocalTr;
		var aoLocal = [];
		var aApplied = [];
		var iColumns = oSettings.aoColumns.length;
		var iRowspan, iColspan;
	
		if ( ! aoSource )
		{
			return;
		}
	
		if (  bIncludeHidden === undefined )
		{
			bIncludeHidden = false;
		}
	
		/* Make a copy of the master layout array, but without the visible columns in it */
		for ( i=0, iLen=aoSource.length ; i<iLen ; i++ )
		{
			aoLocal[i] = aoSource[i].slice();
			aoLocal[i].nTr = aoSource[i].nTr;
	
			/* Remove any columns which are currently hidden */
			for ( j=iColumns-1 ; j>=0 ; j-- )
			{
				if ( !oSettings.aoColumns[j].bVisible && !bIncludeHidden )
				{
					aoLocal[i].splice( j, 1 );
				}
			}
	
			/* Prep the applied array - it needs an element for each row */
			aApplied.push( [] );
		}
	
		for ( i=0, iLen=aoLocal.length ; i<iLen ; i++ )
		{
			nLocalTr = aoLocal[i].nTr;
	
			/* All cells are going to be replaced, so empty out the row */
			if ( nLocalTr )
			{
				while( (n = nLocalTr.firstChild) )
				{
					nLocalTr.removeChild( n );
				}
			}
	
			for ( j=0, jLen=aoLocal[i].length ; j<jLen ; j++ )
			{
				iRowspan = 1;
				iColspan = 1;
	
				/* Check to see if there is already a cell (row/colspan) covering our target
				 * insert point. If there is, then there is nothing to do.
				 */
				if ( aApplied[i][j] === undefined )
				{
					nLocalTr.appendChild( aoLocal[i][j].cell );
					aApplied[i][j] = 1;
	
					/* Expand the cell to cover as many rows as needed */
					while ( aoLocal[i+iRowspan] !== undefined &&
					        aoLocal[i][j].cell == aoLocal[i+iRowspan][j].cell )
					{
						aApplied[i+iRowspan][j] = 1;
						iRowspan++;
					}
	
					/* Expand the cell to cover as many columns as needed */
					while ( aoLocal[i][j+iColspan] !== undefined &&
					        aoLocal[i][j].cell == aoLocal[i][j+iColspan].cell )
					{
						/* Must update the applied array over the rows for the columns */
						for ( k=0 ; k<iRowspan ; k++ )
						{
							aApplied[i+k][j+iColspan] = 1;
						}
						iColspan++;
					}
	
					/* Do the actual expansion in the DOM */
					$(aoLocal[i][j].cell)
						.attr('rowspan', iRowspan)
						.attr('colspan', iColspan);
				}
			}
		}
	}
	
	
	/**
	 * Insert the required TR nodes into the table for display
	 *  @param {object} oSettings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnDraw( oSettings )
	{
		/* Provide a pre-callback function which can be used to cancel the draw is false is returned */
		var aPreDraw = _fnCallbackFire( oSettings, 'aoPreDrawCallback', 'preDraw', [oSettings] );
		if ( $.inArray( false, aPreDraw ) !== -1 )
		{
			_fnProcessingDisplay( oSettings, false );
			return;
		}
	
		var i, iLen, n;
		var anRows = [];
		var iRowCount = 0;
		var asStripeClasses = oSettings.asStripeClasses;
		var iStripes = asStripeClasses.length;
		var iOpenRows = oSettings.aoOpenRows.length;
		var oLang = oSettings.oLanguage;
		var iInitDisplayStart = oSettings.iInitDisplayStart;
		var bServerSide = _fnDataSource( oSettings ) == 'ssp';
		var aiDisplay = oSettings.aiDisplay;
	
		oSettings.bDrawing = true;
	
		/* Check and see if we have an initial draw position from state saving */
		if ( iInitDisplayStart !== undefined && iInitDisplayStart !== -1 )
		{
			oSettings._iDisplayStart = bServerSide ?
				iInitDisplayStart :
				iInitDisplayStart >= oSettings.fnRecordsDisplay() ?
					0 :
					iInitDisplayStart;
	
			oSettings.iInitDisplayStart = -1;
		}
	
		var iDisplayStart = oSettings._iDisplayStart;
		var iDisplayEnd = oSettings.fnDisplayEnd();
	
		/* Server-side processing draw intercept */
		if ( oSettings.bDeferLoading )
		{
			oSettings.bDeferLoading = false;
			oSettings.iDraw++;
			_fnProcessingDisplay( oSettings, false );
		}
		else if ( !bServerSide )
		{
			oSettings.iDraw++;
		}
		else if ( !oSettings.bDestroying && !_fnAjaxUpdate( oSettings ) )
		{
			return;
		}
	
		if ( aiDisplay.length !== 0 )
		{
			var iStart = bServerSide ? 0 : iDisplayStart;
			var iEnd = bServerSide ? oSettings.aoData.length : iDisplayEnd;
	
			for ( var j=iStart ; j<iEnd ; j++ )
			{
				var iDataIndex = aiDisplay[j];
				var aoData = oSettings.aoData[ iDataIndex ];
				if ( aoData.nTr === null )
				{
					_fnCreateTr( oSettings, iDataIndex );
				}
	
				var nRow = aoData.nTr;
	
				/* Remove the old striping classes and then add the new one */
				if ( iStripes !== 0 )
				{
					var sStripe = asStripeClasses[ iRowCount % iStripes ];
					if ( aoData._sRowStripe != sStripe )
					{
						$(nRow).removeClass( aoData._sRowStripe ).addClass( sStripe );
						aoData._sRowStripe = sStripe;
					}
				}
	
				// Row callback functions - might want to manipulate the row
				// iRowCount and j are not currently documented. Are they at all
				// useful?
				_fnCallbackFire( oSettings, 'aoRowCallback', null,
					[nRow, aoData._aData, iRowCount, j] );
	
				anRows.push( nRow );
				iRowCount++;
			}
		}
		else
		{
			/* Table is empty - create a row with an empty message in it */
			var sZero = oLang.sZeroRecords;
			if ( oSettings.iDraw == 1 &&  _fnDataSource( oSettings ) == 'ajax' )
			{
				sZero = oLang.sLoadingRecords;
			}
			else if ( oLang.sEmptyTable && oSettings.fnRecordsTotal() === 0 )
			{
				sZero = oLang.sEmptyTable;
			}
	
			anRows[ 0 ] = $( '<tr/>', { 'class': iStripes ? asStripeClasses[0] : '' } )
				.append( $('<td />', {
					'valign':  'top',
					'colSpan': _fnVisbleColumns( oSettings ),
					'class':   oSettings.oClasses.sRowEmpty
				} ).html( sZero ) )[0];
		}
	
		/* Header and footer callbacks */
		_fnCallbackFire( oSettings, 'aoHeaderCallback', 'header', [ $(oSettings.nTHead).children('tr')[0],
			_fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] );
	
		_fnCallbackFire( oSettings, 'aoFooterCallback', 'footer', [ $(oSettings.nTFoot).children('tr')[0],
			_fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] );
	
		var body = $(oSettings.nTBody);
	
		body.children().detach();
		body.append( $(anRows) );
	
		/* Call all required callback functions for the end of a draw */
		_fnCallbackFire( oSettings, 'aoDrawCallback', 'draw', [oSettings] );
	
		/* Draw is complete, sorting and filtering must be as well */
		oSettings.bSorted = false;
		oSettings.bFiltered = false;
		oSettings.bDrawing = false;
	}
	
	
	/**
	 * Redraw the table - taking account of the various features which are enabled
	 *  @param {object} oSettings dataTables settings object
	 *  @param {boolean} [holdPosition] Keep the current paging position. By default
	 *    the paging is reset to the first page
	 *  @memberof DataTable#oApi
	 */
	function _fnReDraw( settings, holdPosition )
	{
		var
			features = settings.oFeatures,
			sort     = features.bSort,
			filter   = features.bFilter;
	
		if ( sort ) {
			_fnSort( settings );
		}
	
		if ( filter ) {
			_fnFilterComplete( settings, settings.oPreviousSearch );
		}
		else {
			// No filtering, so we want to just use the display master
			settings.aiDisplay = settings.aiDisplayMaster.slice();
		}
	
		if ( holdPosition !== true ) {
			settings._iDisplayStart = 0;
		}
	
		// Let any modules know about the draw hold position state (used by
		// scrolling internally)
		settings._drawHold = holdPosition;
	
		_fnDraw( settings );
	
		settings._drawHold = false;
	}
	
	
	/**
	 * Add the options to the page HTML for the table
	 *  @param {object} oSettings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnAddOptionsHtml ( oSettings )
	{
		var classes = oSettings.oClasses;
		var table = $(oSettings.nTable);
		var holding = $('<div/>').insertBefore( table ); // Holding element for speed
		var features = oSettings.oFeatures;
	
		// All DataTables are wrapped in a div
		var insert = $('<div/>', {
			id:      oSettings.sTableId+'_wrapper',
			'class': classes.sWrapper + (oSettings.nTFoot ? '' : ' '+classes.sNoFooter)
		} );
	
		oSettings.nHolding = holding[0];
		oSettings.nTableWrapper = insert[0];
		oSettings.nTableReinsertBefore = oSettings.nTable.nextSibling;
	
		/* Loop over the user set positioning and place the elements as needed */
		var aDom = oSettings.sDom.split('');
		var featureNode, cOption, nNewNode, cNext, sAttr, j;
		for ( var i=0 ; i<aDom.length ; i++ )
		{
			featureNode = null;
			cOption = aDom[i];
	
			if ( cOption == '<' )
			{
				/* New container div */
				nNewNode = $('<div/>')[0];
	
				/* Check to see if we should append an id and/or a class name to the container */
				cNext = aDom[i+1];
				if ( cNext == "'" || cNext == '"' )
				{
					sAttr = "";
					j = 2;
					while ( aDom[i+j] != cNext )
					{
						sAttr += aDom[i+j];
						j++;
					}
	
					/* Replace jQuery UI constants @todo depreciated */
					if ( sAttr == "H" )
					{
						sAttr = classes.sJUIHeader;
					}
					else if ( sAttr == "F" )
					{
						sAttr = classes.sJUIFooter;
					}
	
					/* The attribute can be in the format of "#id.class", "#id" or "class" This logic
					 * breaks the string into parts and applies them as needed
					 */
					if ( sAttr.indexOf('.') != -1 )
					{
						var aSplit = sAttr.split('.');
						nNewNode.id = aSplit[0].substr(1, aSplit[0].length-1);
						nNewNode.className = aSplit[1];
					}
					else if ( sAttr.charAt(0) == "#" )
					{
						nNewNode.id = sAttr.substr(1, sAttr.length-1);
					}
					else
					{
						nNewNode.className = sAttr;
					}
	
					i += j; /* Move along the position array */
				}
	
				insert.append( nNewNode );
				insert = $(nNewNode);
			}
			else if ( cOption == '>' )
			{
				/* End container div */
				insert = insert.parent();
			}
			// @todo Move options into their own plugins?
			else if ( cOption == 'l' && features.bPaginate && features.bLengthChange )
			{
				/* Length */
				featureNode = _fnFeatureHtmlLength( oSettings );
			}
			else if ( cOption == 'f' && features.bFilter )
			{
				/* Filter */
				featureNode = _fnFeatureHtmlFilter( oSettings );
			}
			else if ( cOption == 'r' && features.bProcessing )
			{
				/* pRocessing */
				featureNode = _fnFeatureHtmlProcessing( oSettings );
			}
			else if ( cOption == 't' )
			{
				/* Table */
				featureNode = _fnFeatureHtmlTable( oSettings );
			}
			else if ( cOption ==  'i' && features.bInfo )
			{
				/* Info */
				featureNode = _fnFeatureHtmlInfo( oSettings );
			}
			else if ( cOption == 'p' && features.bPaginate )
			{
				/* Pagination */
				featureNode = _fnFeatureHtmlPaginate( oSettings );
			}
			else if ( DataTable.ext.feature.length !== 0 )
			{
				/* Plug-in features */
				var aoFeatures = DataTable.ext.feature;
				for ( var k=0, kLen=aoFeatures.length ; k<kLen ; k++ )
				{
					if ( cOption == aoFeatures[k].cFeature )
					{
						featureNode = aoFeatures[k].fnInit( oSettings );
						break;
					}
				}
			}
	
			/* Add to the 2D features array */
			if ( featureNode )
			{
				var aanFeatures = oSettings.aanFeatures;
	
				if ( ! aanFeatures[cOption] )
				{
					aanFeatures[cOption] = [];
				}
	
				aanFeatures[cOption].push( featureNode );
				insert.append( featureNode );
			}
		}
	
		/* Built our DOM structure - replace the holding div with what we want */
		holding.replaceWith( insert );
		oSettings.nHolding = null;
	}
	
	
	/**
	 * Use the DOM source to create up an array of header cells. The idea here is to
	 * create a layout grid (array) of rows x columns, which contains a reference
	 * to the cell that that point in the grid (regardless of col/rowspan), such that
	 * any column / row could be removed and the new grid constructed
	 *  @param array {object} aLayout Array to store the calculated layout in
	 *  @param {node} nThead The header/footer element for the table
	 *  @memberof DataTable#oApi
	 */
	function _fnDetectHeader ( aLayout, nThead )
	{
		var nTrs = $(nThead).children('tr');
		var nTr, nCell;
		var i, k, l, iLen, jLen, iColShifted, iColumn, iColspan, iRowspan;
		var bUnique;
		var fnShiftCol = function ( a, i, j ) {
			var k = a[i];
	                while ( k[j] ) {
				j++;
			}
			return j;
		};
	
		aLayout.splice( 0, aLayout.length );
	
		/* We know how many rows there are in the layout - so prep it */
		for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
		{
			aLayout.push( [] );
		}
	
		/* Calculate a layout array */
		for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
		{
			nTr = nTrs[i];
			iColumn = 0;
	
			/* For every cell in the row... */
			nCell = nTr.firstChild;
			while ( nCell ) {
				if ( nCell.nodeName.toUpperCase() == "TD" ||
				     nCell.nodeName.toUpperCase() == "TH" )
				{
					/* Get the col and rowspan attributes from the DOM and sanitise them */
					iColspan = nCell.getAttribute('colspan') * 1;
					iRowspan = nCell.getAttribute('rowspan') * 1;
					iColspan = (!iColspan || iColspan===0 || iColspan===1) ? 1 : iColspan;
					iRowspan = (!iRowspan || iRowspan===0 || iRowspan===1) ? 1 : iRowspan;
	
					/* There might be colspan cells already in this row, so shift our target
					 * accordingly
					 */
					iColShifted = fnShiftCol( aLayout, i, iColumn );
	
					/* Cache calculation for unique columns */
					bUnique = iColspan === 1 ? true : false;
	
					/* If there is col / rowspan, copy the information into the layout grid */
					for ( l=0 ; l<iColspan ; l++ )
					{
						for ( k=0 ; k<iRowspan ; k++ )
						{
							aLayout[i+k][iColShifted+l] = {
								"cell": nCell,
								"unique": bUnique
							};
							aLayout[i+k].nTr = nTr;
						}
					}
				}
				nCell = nCell.nextSibling;
			}
		}
	}
	
	
	/**
	 * Get an array of unique th elements, one for each column
	 *  @param {object} oSettings dataTables settings object
	 *  @param {node} nHeader automatically detect the layout from this node - optional
	 *  @param {array} aLayout thead/tfoot layout from _fnDetectHeader - optional
	 *  @returns array {node} aReturn list of unique th's
	 *  @memberof DataTable#oApi
	 */
	function _fnGetUniqueThs ( oSettings, nHeader, aLayout )
	{
		var aReturn = [];
		if ( !aLayout )
		{
			aLayout = oSettings.aoHeader;
			if ( nHeader )
			{
				aLayout = [];
				_fnDetectHeader( aLayout, nHeader );
			}
		}
	
		for ( var i=0, iLen=aLayout.length ; i<iLen ; i++ )
		{
			for ( var j=0, jLen=aLayout[i].length ; j<jLen ; j++ )
			{
				if ( aLayout[i][j].unique &&
					 (!aReturn[j] || !oSettings.bSortCellsTop) )
				{
					aReturn[j] = aLayout[i][j].cell;
				}
			}
		}
	
		return aReturn;
	}
	
	/**
	 * Create an Ajax call based on the table's settings, taking into account that
	 * parameters can have multiple forms, and backwards compatibility.
	 *
	 * @param {object} oSettings dataTables settings object
	 * @param {array} data Data to send to the server, required by
	 *     DataTables - may be augmented by developer callbacks
	 * @param {function} fn Callback function to run when data is obtained
	 */
	function _fnBuildAjax( oSettings, data, fn )
	{
		// Compatibility with 1.9-, allow fnServerData and event to manipulate
		_fnCallbackFire( oSettings, 'aoServerParams', 'serverParams', [data] );
	
		// Convert to object based for 1.10+ if using the old array scheme which can
		// come from server-side processing or serverParams
		if ( data && $.isArray(data) ) {
			var tmp = {};
			var rbracket = /(.*?)\[\]$/;
	
			$.each( data, function (key, val) {
				var match = val.name.match(rbracket);
	
				if ( match ) {
					// Support for arrays
					var name = match[0];
	
					if ( ! tmp[ name ] ) {
						tmp[ name ] = [];
					}
					tmp[ name ].push( val.value );
				}
				else {
					tmp[val.name] = val.value;
				}
			} );
			data = tmp;
		}
	
		var ajaxData;
		var ajax = oSettings.ajax;
		var instance = oSettings.oInstance;
		var callback = function ( json ) {
			_fnCallbackFire( oSettings, null, 'xhr', [oSettings, json, oSettings.jqXHR] );
			fn( json );
		};
	
		if ( $.isPlainObject( ajax ) && ajax.data )
		{
			ajaxData = ajax.data;
	
			var newData = $.isFunction( ajaxData ) ?
				ajaxData( data, oSettings ) :  // fn can manipulate data or return
				ajaxData;                      // an object object or array to merge
	
			// If the function returned something, use that alone
			data = $.isFunction( ajaxData ) && newData ?
				newData :
				$.extend( true, data, newData );
	
			// Remove the data property as we've resolved it already and don't want
			// jQuery to do it again (it is restored at the end of the function)
			delete ajax.data;
		}
	
		var baseAjax = {
			"data": data,
			"success": function (json) {
				var error = json.error || json.sError;
				if ( error ) {
					_fnLog( oSettings, 0, error );
				}
	
				oSettings.json = json;
				callback( json );
			},
			"dataType": "json",
			"cache": false,
			"type": oSettings.sServerMethod,
			"error": function (xhr, error, thrown) {
				var ret = _fnCallbackFire( oSettings, null, 'xhr', [oSettings, null, oSettings.jqXHR] );
	
				if ( $.inArray( true, ret ) === -1 ) {
					if ( error == "parsererror" ) {
						_fnLog( oSettings, 0, 'Invalid JSON response', 1 );
					}
					else if ( xhr.readyState === 4 ) {
						_fnLog( oSettings, 0, 'Ajax error', 7 );
					}
				}
	
				_fnProcessingDisplay( oSettings, false );
			}
		};
	
		// Store the data submitted for the API
		oSettings.oAjaxData = data;
	
		// Allow plug-ins and external processes to modify the data
		_fnCallbackFire( oSettings, null, 'preXhr', [oSettings, data] );
	
		if ( oSettings.fnServerData )
		{
			// DataTables 1.9- compatibility
			oSettings.fnServerData.call( instance,
				oSettings.sAjaxSource,
				$.map( data, function (val, key) { // Need to convert back to 1.9 trad format
					return { name: key, value: val };
				} ),
				callback,
				oSettings
			);
		}
		else if ( oSettings.sAjaxSource || typeof ajax === 'string' )
		{
			// DataTables 1.9- compatibility
			oSettings.jqXHR = $.ajax( $.extend( baseAjax, {
				url: ajax || oSettings.sAjaxSource
			} ) );
		}
		else if ( $.isFunction( ajax ) )
		{
			// Is a function - let the caller define what needs to be done
			oSettings.jqXHR = ajax.call( instance, data, callback, oSettings );
		}
		else
		{
			// Object to extend the base settings
			oSettings.jqXHR = $.ajax( $.extend( baseAjax, ajax ) );
	
			// Restore for next time around
			ajax.data = ajaxData;
		}
	}
	
	
	/**
	 * Update the table using an Ajax call
	 *  @param {object} settings dataTables settings object
	 *  @returns {boolean} Block the table drawing or not
	 *  @memberof DataTable#oApi
	 */
	function _fnAjaxUpdate( settings )
	{
		if ( settings.bAjaxDataGet ) {
			settings.iDraw++;
			_fnProcessingDisplay( settings, true );
	
			_fnBuildAjax(
				settings,
				_fnAjaxParameters( settings ),
				function(json) {
					_fnAjaxUpdateDraw( settings, json );
				}
			);
	
			return false;
		}
		return true;
	}
	
	
	/**
	 * Build up the parameters in an object needed for a server-side processing
	 * request. Note that this is basically done twice, is different ways - a modern
	 * method which is used by default in DataTables 1.10 which uses objects and
	 * arrays, or the 1.9- method with is name / value pairs. 1.9 method is used if
	 * the sAjaxSource option is used in the initialisation, or the legacyAjax
	 * option is set.
	 *  @param {object} oSettings dataTables settings object
	 *  @returns {bool} block the table drawing or not
	 *  @memberof DataTable#oApi
	 */
	function _fnAjaxParameters( settings )
	{
		var
			columns = settings.aoColumns,
			columnCount = columns.length,
			features = settings.oFeatures,
			preSearch = settings.oPreviousSearch,
			preColSearch = settings.aoPreSearchCols,
			i, data = [], dataProp, column, columnSearch,
			sort = _fnSortFlatten( settings ),
			displayStart = settings._iDisplayStart,
			displayLength = features.bPaginate !== false ?
				settings._iDisplayLength :
				-1;
	
		var param = function ( name, value ) {
			data.push( { 'name': name, 'value': value } );
		};
	
		// DataTables 1.9- compatible method
		param( 'sEcho',          settings.iDraw );
		param( 'iColumns',       columnCount );
		param( 'sColumns',       _pluck( columns, 'sName' ).join(',') );
		param( 'iDisplayStart',  displayStart );
		param( 'iDisplayLength', displayLength );
	
		// DataTables 1.10+ method
		var d = {
			draw:    settings.iDraw,
			columns: [],
			order:   [],
			start:   displayStart,
			length:  displayLength,
			search:  {
				value: preSearch.sSearch,
				regex: preSearch.bRegex
			}
		};
	
		for ( i=0 ; i<columnCount ; i++ ) {
			column = columns[i];
			columnSearch = preColSearch[i];
			dataProp = typeof column.mData=="function" ? 'function' : column.mData ;
	
			d.columns.push( {
				data:       dataProp,
				name:       column.sName,
				searchable: column.bSearchable,
				orderable:  column.bSortable,
				search:     {
					value: columnSearch.sSearch,
					regex: columnSearch.bRegex
				}
			} );
	
			param( "mDataProp_"+i, dataProp );
	
			if ( features.bFilter ) {
				param( 'sSearch_'+i,     columnSearch.sSearch );
				param( 'bRegex_'+i,      columnSearch.bRegex );
				param( 'bSearchable_'+i, column.bSearchable );
			}
	
			if ( features.bSort ) {
				param( 'bSortable_'+i, column.bSortable );
			}
		}
	
		if ( features.bFilter ) {
			param( 'sSearch', preSearch.sSearch );
			param( 'bRegex', preSearch.bRegex );
		}
	
		if ( features.bSort ) {
			$.each( sort, function ( i, val ) {
				d.order.push( { column: val.col, dir: val.dir } );
	
				param( 'iSortCol_'+i, val.col );
				param( 'sSortDir_'+i, val.dir );
			} );
	
			param( 'iSortingCols', sort.length );
		}
	
		// If the legacy.ajax parameter is null, then we automatically decide which
		// form to use, based on sAjaxSource
		var legacy = DataTable.ext.legacy.ajax;
		if ( legacy === null ) {
			return settings.sAjaxSource ? data : d;
		}
	
		// Otherwise, if legacy has been specified then we use that to decide on the
		// form
		return legacy ? data : d;
	}
	
	
	/**
	 * Data the data from the server (nuking the old) and redraw the table
	 *  @param {object} oSettings dataTables settings object
	 *  @param {object} json json data return from the server.
	 *  @param {string} json.sEcho Tracking flag for DataTables to match requests
	 *  @param {int} json.iTotalRecords Number of records in the data set, not accounting for filtering
	 *  @param {int} json.iTotalDisplayRecords Number of records in the data set, accounting for filtering
	 *  @param {array} json.aaData The data to display on this page
	 *  @param {string} [json.sColumns] Column ordering (sName, comma separated)
	 *  @memberof DataTable#oApi
	 */
	function _fnAjaxUpdateDraw ( settings, json )
	{
		// v1.10 uses camelCase variables, while 1.9 uses Hungarian notation.
		// Support both
		var compat = function ( old, modern ) {
			return json[old] !== undefined ? json[old] : json[modern];
		};
	
		var data = _fnAjaxDataSrc( settings, json );
		var draw            = compat( 'sEcho',                'draw' );
		var recordsTotal    = compat( 'iTotalRecords',        'recordsTotal' );
		var recordsFiltered = compat( 'iTotalDisplayRecords', 'recordsFiltered' );
	
		if ( draw ) {
			// Protect against out of sequence returns
			if ( draw*1 < settings.iDraw ) {
				return;
			}
			settings.iDraw = draw * 1;
		}
	
		_fnClearTable( settings );
		settings._iRecordsTotal   = parseInt(recordsTotal, 10);
		settings._iRecordsDisplay = parseInt(recordsFiltered, 10);
	
		for ( var i=0, ien=data.length ; i<ien ; i++ ) {
			_fnAddData( settings, data[i] );
		}
		settings.aiDisplay = settings.aiDisplayMaster.slice();
	
		settings.bAjaxDataGet = false;
		_fnDraw( settings );
	
		if ( ! settings._bInitComplete ) {
			_fnInitComplete( settings, json );
		}
	
		settings.bAjaxDataGet = true;
		_fnProcessingDisplay( settings, false );
	}
	
	
	/**
	 * Get the data from the JSON data source to use for drawing a table. Using
	 * `_fnGetObjectDataFn` allows the data to be sourced from a property of the
	 * source object, or from a processing function.
	 *  @param {object} oSettings dataTables settings object
	 *  @param  {object} json Data source object / array from the server
	 *  @return {array} Array of data to use
	 */
	function _fnAjaxDataSrc ( oSettings, json )
	{
		var dataSrc = $.isPlainObject( oSettings.ajax ) && oSettings.ajax.dataSrc !== undefined ?
			oSettings.ajax.dataSrc :
			oSettings.sAjaxDataProp; // Compatibility with 1.9-.
	
		// Compatibility with 1.9-. In order to read from aaData, check if the
		// default has been changed, if not, check for aaData
		if ( dataSrc === 'data' ) {
			return json.aaData || json[dataSrc];
		}
	
		return dataSrc !== "" ?
			_fnGetObjectDataFn( dataSrc )( json ) :
			json;
	}
	
	/**
	 * Generate the node required for filtering text
	 *  @returns {node} Filter control element
	 *  @param {object} oSettings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnFeatureHtmlFilter ( settings )
	{
		var classes = settings.oClasses;
		var tableId = settings.sTableId;
		var language = settings.oLanguage;
		var previousSearch = settings.oPreviousSearch;
		var features = settings.aanFeatures;
		var input = '<input type="search" class="'+classes.sFilterInput+'"/>';
	
		var str = language.sSearch;
		str = str.match(/_INPUT_/) ?
			str.replace('_INPUT_', input) :
			str+input;
	
		var filter = $('<div/>', {
				'id': ! features.f ? tableId+'_filter' : null,
				'class': classes.sFilter
			} )
			.append( $('<label/>' ).append( str ) );
	
		var searchFn = function() {
			/* Update all other filter input elements for the new display */
			var n = features.f;
			var val = !this.value ? "" : this.value; // mental IE8 fix :-(
	
			/* Now do the filter */
			if ( val != previousSearch.sSearch ) {
				_fnFilterComplete( settings, {
					"sSearch": val,
					"bRegex": previousSearch.bRegex,
					"bSmart": previousSearch.bSmart ,
					"bCaseInsensitive": previousSearch.bCaseInsensitive
				} );
	
				// Need to redraw, without resorting
				settings._iDisplayStart = 0;
				_fnDraw( settings );
			}
		};
	
		var searchDelay = settings.searchDelay !== null ?
			settings.searchDelay :
			_fnDataSource( settings ) === 'ssp' ?
				400 :
				0;
	
		var jqFilter = $('input', filter)
			.val( previousSearch.sSearch )
			.attr( 'placeholder', language.sSearchPlaceholder )
			.bind(
				'keyup.DT search.DT input.DT paste.DT cut.DT',
				searchDelay ?
					_fnThrottle( searchFn, searchDelay ) :
					searchFn
			)
			.bind( 'keypress.DT', function(e) {
				/* Prevent form submission */
				if ( e.keyCode == 13 ) {
					return false;
				}
			} )
			.attr('aria-controls', tableId);
	
		// Update the input elements whenever the table is filtered
		$(settings.nTable).on( 'search.dt.DT', function ( ev, s ) {
			if ( settings === s ) {
				// IE9 throws an 'unknown error' if document.activeElement is used
				// inside an iframe or frame...
				try {
					if ( jqFilter[0] !== document.activeElement ) {
						jqFilter.val( previousSearch.sSearch );
					}
				}
				catch ( e ) {}
			}
		} );
	
		return filter[0];
	}
	
	
	/**
	 * Filter the table using both the global filter and column based filtering
	 *  @param {object} oSettings dataTables settings object
	 *  @param {object} oSearch search information
	 *  @param {int} [iForce] force a research of the master array (1) or not (undefined or 0)
	 *  @memberof DataTable#oApi
	 */
	function _fnFilterComplete ( oSettings, oInput, iForce )
	{
		var oPrevSearch = oSettings.oPreviousSearch;
		var aoPrevSearch = oSettings.aoPreSearchCols;
		var fnSaveFilter = function ( oFilter ) {
			/* Save the filtering values */
			oPrevSearch.sSearch = oFilter.sSearch;
			oPrevSearch.bRegex = oFilter.bRegex;
			oPrevSearch.bSmart = oFilter.bSmart;
			oPrevSearch.bCaseInsensitive = oFilter.bCaseInsensitive;
		};
		var fnRegex = function ( o ) {
			// Backwards compatibility with the bEscapeRegex option
			return o.bEscapeRegex !== undefined ? !o.bEscapeRegex : o.bRegex;
		};
	
		// Resolve any column types that are unknown due to addition or invalidation
		// @todo As per sort - can this be moved into an event handler?
		_fnColumnTypes( oSettings );
	
		/* In server-side processing all filtering is done by the server, so no point hanging around here */
		if ( _fnDataSource( oSettings ) != 'ssp' )
		{
			/* Global filter */
			_fnFilter( oSettings, oInput.sSearch, iForce, fnRegex(oInput), oInput.bSmart, oInput.bCaseInsensitive );
			fnSaveFilter( oInput );
	
			/* Now do the individual column filter */
			for ( var i=0 ; i<aoPrevSearch.length ; i++ )
			{
				_fnFilterColumn( oSettings, aoPrevSearch[i].sSearch, i, fnRegex(aoPrevSearch[i]),
					aoPrevSearch[i].bSmart, aoPrevSearch[i].bCaseInsensitive );
			}
	
			/* Custom filtering */
			_fnFilterCustom( oSettings );
		}
		else
		{
			fnSaveFilter( oInput );
		}
	
		/* Tell the draw function we have been filtering */
		oSettings.bFiltered = true;
		_fnCallbackFire( oSettings, null, 'search', [oSettings] );
	}
	
	
	/**
	 * Apply custom filtering functions
	 *  @param {object} oSettings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnFilterCustom( settings )
	{
		var filters = DataTable.ext.search;
		var displayRows = settings.aiDisplay;
		var row, rowIdx;
	
		for ( var i=0, ien=filters.length ; i<ien ; i++ ) {
			var rows = [];
	
			// Loop over each row and see if it should be included
			for ( var j=0, jen=displayRows.length ; j<jen ; j++ ) {
				rowIdx = displayRows[ j ];
				row = settings.aoData[ rowIdx ];
	
				if ( filters[i]( settings, row._aFilterData, rowIdx, row._aData, j ) ) {
					rows.push( rowIdx );
				}
			}
	
			// So the array reference doesn't break set the results into the
			// existing array
			displayRows.length = 0;
			$.merge( displayRows, rows );
		}
	}
	
	
	/**
	 * Filter the table on a per-column basis
	 *  @param {object} oSettings dataTables settings object
	 *  @param {string} sInput string to filter on
	 *  @param {int} iColumn column to filter
	 *  @param {bool} bRegex treat search string as a regular expression or not
	 *  @param {bool} bSmart use smart filtering or not
	 *  @param {bool} bCaseInsensitive Do case insenstive matching or not
	 *  @memberof DataTable#oApi
	 */
	function _fnFilterColumn ( settings, searchStr, colIdx, regex, smart, caseInsensitive )
	{
		if ( searchStr === '' ) {
			return;
		}
	
		var data;
		var display = settings.aiDisplay;
		var rpSearch = _fnFilterCreateSearch( searchStr, regex, smart, caseInsensitive );
	
		for ( var i=display.length-1 ; i>=0 ; i-- ) {
			data = settings.aoData[ display[i] ]._aFilterData[ colIdx ];
	
			if ( ! rpSearch.test( data ) ) {
				display.splice( i, 1 );
			}
		}
	}
	
	
	/**
	 * Filter the data table based on user input and draw the table
	 *  @param {object} settings dataTables settings object
	 *  @param {string} input string to filter on
	 *  @param {int} force optional - force a research of the master array (1) or not (undefined or 0)
	 *  @param {bool} regex treat as a regular expression or not
	 *  @param {bool} smart perform smart filtering or not
	 *  @param {bool} caseInsensitive Do case insenstive matching or not
	 *  @memberof DataTable#oApi
	 */
	function _fnFilter( settings, input, force, regex, smart, caseInsensitive )
	{
		var rpSearch = _fnFilterCreateSearch( input, regex, smart, caseInsensitive );
		var prevSearch = settings.oPreviousSearch.sSearch;
		var displayMaster = settings.aiDisplayMaster;
		var display, invalidated, i;
	
		// Need to take account of custom filtering functions - always filter
		if ( DataTable.ext.search.length !== 0 ) {
			force = true;
		}
	
		// Check if any of the rows were invalidated
		invalidated = _fnFilterData( settings );
	
		// If the input is blank - we just want the full data set
		if ( input.length <= 0 ) {
			settings.aiDisplay = displayMaster.slice();
		}
		else {
			// New search - start from the master array
			if ( invalidated ||
				 force ||
				 prevSearch.length > input.length ||
				 input.indexOf(prevSearch) !== 0 ||
				 settings.bSorted // On resort, the display master needs to be
				                  // re-filtered since indexes will have changed
			) {
				settings.aiDisplay = displayMaster.slice();
			}
	
			// Search the display array
			display = settings.aiDisplay;
	
			for ( i=display.length-1 ; i>=0 ; i-- ) {
				if ( ! rpSearch.test( settings.aoData[ display[i] ]._sFilterRow ) ) {
					display.splice( i, 1 );
				}
			}
		}
	}
	
	
	/**
	 * Build a regular expression object suitable for searching a table
	 *  @param {string} sSearch string to search for
	 *  @param {bool} bRegex treat as a regular expression or not
	 *  @param {bool} bSmart perform smart filtering or not
	 *  @param {bool} bCaseInsensitive Do case insensitive matching or not
	 *  @returns {RegExp} constructed object
	 *  @memberof DataTable#oApi
	 */
	function _fnFilterCreateSearch( search, regex, smart, caseInsensitive )
	{
		search = regex ?
			search :
			_fnEscapeRegex( search );
		
		if ( smart ) {
			/* For smart filtering we want to allow the search to work regardless of
			 * word order. We also want double quoted text to be preserved, so word
			 * order is important - a la google. So this is what we want to
			 * generate:
			 * 
			 * ^(?=.*?\bone\b)(?=.*?\btwo three\b)(?=.*?\bfour\b).*$
			 */
			var a = $.map( search.match( /"[^"]+"|[^ ]+/g ) || [''], function ( word ) {
				if ( word.charAt(0) === '"' ) {
					var m = word.match( /^"(.*)"$/ );
					word = m ? m[1] : word;
				}
	
				return word.replace('"', '');
			} );
	
			search = '^(?=.*?'+a.join( ')(?=.*?' )+').*$';
		}
	
		return new RegExp( search, caseInsensitive ? 'i' : '' );
	}
	
	
	/**
	 * Escape a string such that it can be used in a regular expression
	 *  @param {string} sVal string to escape
	 *  @returns {string} escaped string
	 *  @memberof DataTable#oApi
	 */
	var _fnEscapeRegex = DataTable.util.escapeRegex;
	
	var __filter_div = $('<div>')[0];
	var __filter_div_textContent = __filter_div.textContent !== undefined;
	
	// Update the filtering data for each row if needed (by invalidation or first run)
	function _fnFilterData ( settings )
	{
		var columns = settings.aoColumns;
		var column;
		var i, j, ien, jen, filterData, cellData, row;
		var fomatters = DataTable.ext.type.search;
		var wasInvalidated = false;
	
		for ( i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
			row = settings.aoData[i];
	
			if ( ! row._aFilterData ) {
				filterData = [];
	
				for ( j=0, jen=columns.length ; j<jen ; j++ ) {
					column = columns[j];
	
					if ( column.bSearchable ) {
						cellData = _fnGetCellData( settings, i, j, 'filter' );
	
						if ( fomatters[ column.sType ] ) {
							cellData = fomatters[ column.sType ]( cellData );
						}
	
						// Search in DataTables 1.10 is string based. In 1.11 this
						// should be altered to also allow strict type checking.
						if ( cellData === null ) {
							cellData = '';
						}
	
						if ( typeof cellData !== 'string' && cellData.toString ) {
							cellData = cellData.toString();
						}
					}
					else {
						cellData = '';
					}
	
					// If it looks like there is an HTML entity in the string,
					// attempt to decode it so sorting works as expected. Note that
					// we could use a single line of jQuery to do this, but the DOM
					// method used here is much faster http://jsperf.com/html-decode
					if ( cellData.indexOf && cellData.indexOf('&') !== -1 ) {
						__filter_div.innerHTML = cellData;
						cellData = __filter_div_textContent ?
							__filter_div.textContent :
							__filter_div.innerText;
					}
	
					if ( cellData.replace ) {
						cellData = cellData.replace(/[\r\n]/g, '');
					}
	
					filterData.push( cellData );
				}
	
				row._aFilterData = filterData;
				row._sFilterRow = filterData.join('  ');
				wasInvalidated = true;
			}
		}
	
		return wasInvalidated;
	}
	
	
	/**
	 * Convert from the internal Hungarian notation to camelCase for external
	 * interaction
	 *  @param {object} obj Object to convert
	 *  @returns {object} Inverted object
	 *  @memberof DataTable#oApi
	 */
	function _fnSearchToCamel ( obj )
	{
		return {
			search:          obj.sSearch,
			smart:           obj.bSmart,
			regex:           obj.bRegex,
			caseInsensitive: obj.bCaseInsensitive
		};
	}
	
	
	
	/**
	 * Convert from camelCase notation to the internal Hungarian. We could use the
	 * Hungarian convert function here, but this is cleaner
	 *  @param {object} obj Object to convert
	 *  @returns {object} Inverted object
	 *  @memberof DataTable#oApi
	 */
	function _fnSearchToHung ( obj )
	{
		return {
			sSearch:          obj.search,
			bSmart:           obj.smart,
			bRegex:           obj.regex,
			bCaseInsensitive: obj.caseInsensitive
		};
	}
	
	/**
	 * Generate the node required for the info display
	 *  @param {object} oSettings dataTables settings object
	 *  @returns {node} Information element
	 *  @memberof DataTable#oApi
	 */
	function _fnFeatureHtmlInfo ( settings )
	{
		var
			tid = settings.sTableId,
			nodes = settings.aanFeatures.i,
			n = $('<div/>', {
				'class': settings.oClasses.sInfo,
				'id': ! nodes ? tid+'_info' : null
			} );
	
		if ( ! nodes ) {
			// Update display on each draw
			settings.aoDrawCallback.push( {
				"fn": _fnUpdateInfo,
				"sName": "information"
			} );
	
			n
				.attr( 'role', 'status' )
				.attr( 'aria-live', 'polite' );
	
			// Table is described by our info div
			$(settings.nTable).attr( 'aria-describedby', tid+'_info' );
		}
	
		return n[0];
	}
	
	
	/**
	 * Update the information elements in the display
	 *  @param {object} settings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnUpdateInfo ( settings )
	{
		/* Show information about the table */
		var nodes = settings.aanFeatures.i;
		if ( nodes.length === 0 ) {
			return;
		}
	
		var
			lang  = settings.oLanguage,
			start = settings._iDisplayStart+1,
			end   = settings.fnDisplayEnd(),
			max   = settings.fnRecordsTotal(),
			total = settings.fnRecordsDisplay(),
			out   = total ?
				lang.sInfo :
				lang.sInfoEmpty;
	
		if ( total !== max ) {
			/* Record set after filtering */
			out += ' ' + lang.sInfoFiltered;
		}
	
		// Convert the macros
		out += lang.sInfoPostFix;
		out = _fnInfoMacros( settings, out );
	
		var callback = lang.fnInfoCallback;
		if ( callback !== null ) {
			out = callback.call( settings.oInstance,
				settings, start, end, max, total, out
			);
		}
	
		$(nodes).html( out );
	}
	
	
	function _fnInfoMacros ( settings, str )
	{
		// When infinite scrolling, we are always starting at 1. _iDisplayStart is used only
		// internally
		var
			formatter  = settings.fnFormatNumber,
			start      = settings._iDisplayStart+1,
			len        = settings._iDisplayLength,
			vis        = settings.fnRecordsDisplay(),
			all        = len === -1;
	
		return str.
			replace(/_START_/g, formatter.call( settings, start ) ).
			replace(/_END_/g,   formatter.call( settings, settings.fnDisplayEnd() ) ).
			replace(/_MAX_/g,   formatter.call( settings, settings.fnRecordsTotal() ) ).
			replace(/_TOTAL_/g, formatter.call( settings, vis ) ).
			replace(/_PAGE_/g,  formatter.call( settings, all ? 1 : Math.ceil( start / len ) ) ).
			replace(/_PAGES_/g, formatter.call( settings, all ? 1 : Math.ceil( vis / len ) ) );
	}
	
	
	
	/**
	 * Draw the table for the first time, adding all required features
	 *  @param {object} settings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnInitialise ( settings )
	{
		var i, iLen, iAjaxStart=settings.iInitDisplayStart;
		var columns = settings.aoColumns, column;
		var features = settings.oFeatures;
		var deferLoading = settings.bDeferLoading; // value modified by the draw
	
		/* Ensure that the table data is fully initialised */
		if ( ! settings.bInitialised ) {
			setTimeout( function(){ _fnInitialise( settings ); }, 200 );
			return;
		}
	
		/* Show the display HTML options */
		_fnAddOptionsHtml( settings );
	
		/* Build and draw the header / footer for the table */
		_fnBuildHead( settings );
		_fnDrawHead( settings, settings.aoHeader );
		_fnDrawHead( settings, settings.aoFooter );
	
		/* Okay to show that something is going on now */
		_fnProcessingDisplay( settings, true );
	
		/* Calculate sizes for columns */
		if ( features.bAutoWidth ) {
			_fnCalculateColumnWidths( settings );
		}
	
		for ( i=0, iLen=columns.length ; i<iLen ; i++ ) {
			column = columns[i];
	
			if ( column.sWidth ) {
				column.nTh.style.width = _fnStringToCss( column.sWidth );
			}
		}
	
		_fnCallbackFire( settings, null, 'preInit', [settings] );
	
		// If there is default sorting required - let's do it. The sort function
		// will do the drawing for us. Otherwise we draw the table regardless of the
		// Ajax source - this allows the table to look initialised for Ajax sourcing
		// data (show 'loading' message possibly)
		_fnReDraw( settings );
	
		// Server-side processing init complete is done by _fnAjaxUpdateDraw
		var dataSrc = _fnDataSource( settings );
		if ( dataSrc != 'ssp' || deferLoading ) {
			// if there is an ajax source load the data
			if ( dataSrc == 'ajax' ) {
				_fnBuildAjax( settings, [], function(json) {
					var aData = _fnAjaxDataSrc( settings, json );
	
					// Got the data - add it to the table
					for ( i=0 ; i<aData.length ; i++ ) {
						_fnAddData( settings, aData[i] );
					}
	
					// Reset the init display for cookie saving. We've already done
					// a filter, and therefore cleared it before. So we need to make
					// it appear 'fresh'
					settings.iInitDisplayStart = iAjaxStart;
	
					_fnReDraw( settings );
	
					_fnProcessingDisplay( settings, false );
					_fnInitComplete( settings, json );
				}, settings );
			}
			else {
				_fnProcessingDisplay( settings, false );
				_fnInitComplete( settings );
			}
		}
	}
	
	
	/**
	 * Draw the table for the first time, adding all required features
	 *  @param {object} oSettings dataTables settings object
	 *  @param {object} [json] JSON from the server that completed the table, if using Ajax source
	 *    with client-side processing (optional)
	 *  @memberof DataTable#oApi
	 */
	function _fnInitComplete ( settings, json )
	{
		settings._bInitComplete = true;
	
		// When data was added after the initialisation (data or Ajax) we need to
		// calculate the column sizing
		if ( json || settings.oInit.aaData ) {
			_fnAdjustColumnSizing( settings );
		}
	
		_fnCallbackFire( settings, null, 'plugin-init', [settings, json] );
		_fnCallbackFire( settings, 'aoInitComplete', 'init', [settings, json] );
	}
	
	
	function _fnLengthChange ( settings, val )
	{
		var len = parseInt( val, 10 );
		settings._iDisplayLength = len;
	
		_fnLengthOverflow( settings );
	
		// Fire length change event
		_fnCallbackFire( settings, null, 'length', [settings, len] );
	}
	
	
	/**
	 * Generate the node required for user display length changing
	 *  @param {object} settings dataTables settings object
	 *  @returns {node} Display length feature node
	 *  @memberof DataTable#oApi
	 */
	function _fnFeatureHtmlLength ( settings )
	{
		var
			classes  = settings.oClasses,
			tableId  = settings.sTableId,
			menu     = settings.aLengthMenu,
			d2       = $.isArray( menu[0] ),
			lengths  = d2 ? menu[0] : menu,
			language = d2 ? menu[1] : menu;
	
		var select = $('<select/>', {
			'name':          tableId+'_length',
			'aria-controls': tableId,
			'class':         classes.sLengthSelect
		} );
	
		for ( var i=0, ien=lengths.length ; i<ien ; i++ ) {
			select[0][ i ] = new Option( language[i], lengths[i] );
		}
	
		var div = $('<div><label/></div>').addClass( classes.sLength );
		if ( ! settings.aanFeatures.l ) {
			div[0].id = tableId+'_length';
		}
	
		div.children().append(
			settings.oLanguage.sLengthMenu.replace( '_MENU_', select[0].outerHTML )
		);
	
		// Can't use `select` variable as user might provide their own and the
		// reference is broken by the use of outerHTML
		$('select', div)
			.val( settings._iDisplayLength )
			.bind( 'change.DT', function(e) {
				_fnLengthChange( settings, $(this).val() );
				_fnDraw( settings );
			} );
	
		// Update node value whenever anything changes the table's length
		$(settings.nTable).bind( 'length.dt.DT', function (e, s, len) {
			if ( settings === s ) {
				$('select', div).val( len );
			}
		} );
	
		return div[0];
	}
	
	
	
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Note that most of the paging logic is done in
	 * DataTable.ext.pager
	 */
	
	/**
	 * Generate the node required for default pagination
	 *  @param {object} oSettings dataTables settings object
	 *  @returns {node} Pagination feature node
	 *  @memberof DataTable#oApi
	 */
	function _fnFeatureHtmlPaginate ( settings )
	{
		var
			type   = settings.sPaginationType,
			plugin = DataTable.ext.pager[ type ],
			modern = typeof plugin === 'function',
			redraw = function( settings ) {
				_fnDraw( settings );
			},
			node = $('<div/>').addClass( settings.oClasses.sPaging + type )[0],
			features = settings.aanFeatures;
	
		if ( ! modern ) {
			plugin.fnInit( settings, node, redraw );
		}
	
		/* Add a draw callback for the pagination on first instance, to update the paging display */
		if ( ! features.p )
		{
			node.id = settings.sTableId+'_paginate';
	
			settings.aoDrawCallback.push( {
				"fn": function( settings ) {
					if ( modern ) {
						var
							start      = settings._iDisplayStart,
							len        = settings._iDisplayLength,
							visRecords = settings.fnRecordsDisplay(),
							all        = len === -1,
							page = all ? 0 : Math.ceil( start / len ),
							pages = all ? 1 : Math.ceil( visRecords / len ),
							buttons = plugin(page, pages),
							i, ien;
	
						for ( i=0, ien=features.p.length ; i<ien ; i++ ) {
							_fnRenderer( settings, 'pageButton' )(
								settings, features.p[i], i, buttons, page, pages
							);
						}
					}
					else {
						plugin.fnUpdate( settings, redraw );
					}
				},
				"sName": "pagination"
			} );
		}
	
		return node;
	}
	
	
	/**
	 * Alter the display settings to change the page
	 *  @param {object} settings DataTables settings object
	 *  @param {string|int} action Paging action to take: "first", "previous",
	 *    "next" or "last" or page number to jump to (integer)
	 *  @param [bool] redraw Automatically draw the update or not
	 *  @returns {bool} true page has changed, false - no change
	 *  @memberof DataTable#oApi
	 */
	function _fnPageChange ( settings, action, redraw )
	{
		var
			start     = settings._iDisplayStart,
			len       = settings._iDisplayLength,
			records   = settings.fnRecordsDisplay();
	
		if ( records === 0 || len === -1 )
		{
			start = 0;
		}
		else if ( typeof action === "number" )
		{
			start = action * len;
	
			if ( start > records )
			{
				start = 0;
			}
		}
		else if ( action == "first" )
		{
			start = 0;
		}
		else if ( action == "previous" )
		{
			start = len >= 0 ?
				start - len :
				0;
	
			if ( start < 0 )
			{
			  start = 0;
			}
		}
		else if ( action == "next" )
		{
			if ( start + len < records )
			{
				start += len;
			}
		}
		else if ( action == "last" )
		{
			start = Math.floor( (records-1) / len) * len;
		}
		else
		{
			_fnLog( settings, 0, "Unknown paging action: "+action, 5 );
		}
	
		var changed = settings._iDisplayStart !== start;
		settings._iDisplayStart = start;
	
		if ( changed ) {
			_fnCallbackFire( settings, null, 'page', [settings] );
	
			if ( redraw ) {
				_fnDraw( settings );
			}
		}
	
		return changed;
	}
	
	
	
	/**
	 * Generate the node required for the processing node
	 *  @param {object} settings dataTables settings object
	 *  @returns {node} Processing element
	 *  @memberof DataTable#oApi
	 */
	function _fnFeatureHtmlProcessing ( settings )
	{
		return $('<div/>', {
				'id': ! settings.aanFeatures.r ? settings.sTableId+'_processing' : null,
				'class': settings.oClasses.sProcessing
			} )
			.html( settings.oLanguage.sProcessing )
			.insertBefore( settings.nTable )[0];
	}
	
	
	/**
	 * Display or hide the processing indicator
	 *  @param {object} settings dataTables settings object
	 *  @param {bool} show Show the processing indicator (true) or not (false)
	 *  @memberof DataTable#oApi
	 */
	function _fnProcessingDisplay ( settings, show )
	{
		if ( settings.oFeatures.bProcessing ) {
			$(settings.aanFeatures.r).css( 'display', show ? 'block' : 'none' );
		}
	
		_fnCallbackFire( settings, null, 'processing', [settings, show] );
	}
	
	/**
	 * Add any control elements for the table - specifically scrolling
	 *  @param {object} settings dataTables settings object
	 *  @returns {node} Node to add to the DOM
	 *  @memberof DataTable#oApi
	 */
	function _fnFeatureHtmlTable ( settings )
	{
		var table = $(settings.nTable);
	
		// Add the ARIA grid role to the table
		table.attr( 'role', 'grid' );
	
		// Scrolling from here on in
		var scroll = settings.oScroll;
	
		if ( scroll.sX === '' && scroll.sY === '' ) {
			return settings.nTable;
		}
	
		var scrollX = scroll.sX;
		var scrollY = scroll.sY;
		var classes = settings.oClasses;
		var caption = table.children('caption');
		var captionSide = caption.length ? caption[0]._captionSide : null;
		var headerClone = $( table[0].cloneNode(false) );
		var footerClone = $( table[0].cloneNode(false) );
		var footer = table.children('tfoot');
		var _div = '<div/>';
		var size = function ( s ) {
			return !s ? null : _fnStringToCss( s );
		};
	
		if ( ! footer.length ) {
			footer = null;
		}
	
		/*
		 * The HTML structure that we want to generate in this function is:
		 *  div - scroller
		 *    div - scroll head
		 *      div - scroll head inner
		 *        table - scroll head table
		 *          thead - thead
		 *    div - scroll body
		 *      table - table (master table)
		 *        thead - thead clone for sizing
		 *        tbody - tbody
		 *    div - scroll foot
		 *      div - scroll foot inner
		 *        table - scroll foot table
		 *          tfoot - tfoot
		 */
		var scroller = $( _div, { 'class': classes.sScrollWrapper } )
			.append(
				$(_div, { 'class': classes.sScrollHead } )
					.css( {
						overflow: 'hidden',
						position: 'relative',
						border: 0,
						width: scrollX ? size(scrollX) : '100%'
					} )
					.append(
						$(_div, { 'class': classes.sScrollHeadInner } )
							.css( {
								'box-sizing': 'content-box',
								width: scroll.sXInner || '100%'
							} )
							.append(
								headerClone
									.removeAttr('id')
									.css( 'margin-left', 0 )
									.append( captionSide === 'top' ? caption : null )
									.append(
										table.children('thead')
									)
							)
					)
			)
			.append(
				$(_div, { 'class': classes.sScrollBody } )
					.css( {
						position: 'relative',
						overflow: 'auto',
						width: size( scrollX )
					} )
					.append( table )
			);
	
		if ( footer ) {
			scroller.append(
				$(_div, { 'class': classes.sScrollFoot } )
					.css( {
						overflow: 'hidden',
						border: 0,
						width: scrollX ? size(scrollX) : '100%'
					} )
					.append(
						$(_div, { 'class': classes.sScrollFootInner } )
							.append(
								footerClone
									.removeAttr('id')
									.css( 'margin-left', 0 )
									.append( captionSide === 'bottom' ? caption : null )
									.append(
										table.children('tfoot')
									)
							)
					)
			);
		}
	
		var children = scroller.children();
		var scrollHead = children[0];
		var scrollBody = children[1];
		var scrollFoot = footer ? children[2] : null;
	
		// When the body is scrolled, then we also want to scroll the headers
		if ( scrollX ) {
			$(scrollBody).on( 'scroll.DT', function (e) {
				var scrollLeft = this.scrollLeft;
	
				scrollHead.scrollLeft = scrollLeft;
	
				if ( footer ) {
					scrollFoot.scrollLeft = scrollLeft;
				}
			} );
		}
	
		$(scrollBody).css(
			scrollY && scroll.bCollapse ? 'max-height' : 'height', 
			scrollY
		);
	
		settings.nScrollHead = scrollHead;
		settings.nScrollBody = scrollBody;
		settings.nScrollFoot = scrollFoot;
	
		// On redraw - align columns
		settings.aoDrawCallback.push( {
			"fn": _fnScrollDraw,
			"sName": "scrolling"
		} );
	
		return scroller[0];
	}
	
	
	
	/**
	 * Update the header, footer and body tables for resizing - i.e. column
	 * alignment.
	 *
	 * Welcome to the most horrible function DataTables. The process that this
	 * function follows is basically:
	 *   1. Re-create the table inside the scrolling div
	 *   2. Take live measurements from the DOM
	 *   3. Apply the measurements to align the columns
	 *   4. Clean up
	 *
	 *  @param {object} settings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnScrollDraw ( settings )
	{
		// Given that this is such a monster function, a lot of variables are use
		// to try and keep the minimised size as small as possible
		var
			scroll         = settings.oScroll,
			scrollX        = scroll.sX,
			scrollXInner   = scroll.sXInner,
			scrollY        = scroll.sY,
			barWidth       = scroll.iBarWidth,
			divHeader      = $(settings.nScrollHead),
			divHeaderStyle = divHeader[0].style,
			divHeaderInner = divHeader.children('div'),
			divHeaderInnerStyle = divHeaderInner[0].style,
			divHeaderTable = divHeaderInner.children('table'),
			divBodyEl      = settings.nScrollBody,
			divBody        = $(divBodyEl),
			divBodyStyle   = divBodyEl.style,
			divFooter      = $(settings.nScrollFoot),
			divFooterInner = divFooter.children('div'),
			divFooterTable = divFooterInner.children('table'),
			header         = $(settings.nTHead),
			table          = $(settings.nTable),
			tableEl        = table[0],
			tableStyle     = tableEl.style,
			footer         = settings.nTFoot ? $(settings.nTFoot) : null,
			browser        = settings.oBrowser,
			ie67           = browser.bScrollOversize,
			dtHeaderCells  = _pluck( settings.aoColumns, 'nTh' ),
			headerTrgEls, footerTrgEls,
			headerSrcEls, footerSrcEls,
			headerCopy, footerCopy,
			headerWidths=[], footerWidths=[],
			headerContent=[], footerContent=[],
			idx, correction, sanityWidth,
			zeroOut = function(nSizer) {
				var style = nSizer.style;
				style.paddingTop = "0";
				style.paddingBottom = "0";
				style.borderTopWidth = "0";
				style.borderBottomWidth = "0";
				style.height = 0;
			};
	
		// If the scrollbar visibility has changed from the last draw, we need to
		// adjust the column sizes as the table width will have changed to account
		// for the scrollbar
		var scrollBarVis = divBodyEl.scrollHeight > divBodyEl.clientHeight;
		
		if ( settings.scrollBarVis !== scrollBarVis && settings.scrollBarVis !== undefined ) {
			settings.scrollBarVis = scrollBarVis;
			_fnAdjustColumnSizing( settings );
			return; // adjust column sizing will call this function again
		}
		else {
			settings.scrollBarVis = scrollBarVis;
		}
	
		/*
		 * 1. Re-create the table inside the scrolling div
		 */
	
		// Remove the old minimised thead and tfoot elements in the inner table
		table.children('thead, tfoot').remove();
	
		if ( footer ) {
			footerCopy = footer.clone().prependTo( table );
			footerTrgEls = footer.find('tr'); // the original tfoot is in its own table and must be sized
			footerSrcEls = footerCopy.find('tr');
		}
	
		// Clone the current header and footer elements and then place it into the inner table
		headerCopy = header.clone().prependTo( table );
		headerTrgEls = header.find('tr'); // original header is in its own table
		headerSrcEls = headerCopy.find('tr');
		headerCopy.find('th, td').removeAttr('tabindex');
	
	
		/*
		 * 2. Take live measurements from the DOM - do not alter the DOM itself!
		 */
	
		// Remove old sizing and apply the calculated column widths
		// Get the unique column headers in the newly created (cloned) header. We want to apply the
		// calculated sizes to this header
		if ( ! scrollX )
		{
			divBodyStyle.width = '100%';
			divHeader[0].style.width = '100%';
		}
	
		$.each( _fnGetUniqueThs( settings, headerCopy ), function ( i, el ) {
			idx = _fnVisibleToColumnIndex( settings, i );
			el.style.width = settings.aoColumns[idx].sWidth;
		} );
	
		if ( footer ) {
			_fnApplyToChildren( function(n) {
				n.style.width = "";
			}, footerSrcEls );
		}
	
		// Size the table as a whole
		sanityWidth = table.outerWidth();
		if ( scrollX === "" ) {
			// No x scrolling
			tableStyle.width = "100%";
	
			// IE7 will make the width of the table when 100% include the scrollbar
			// - which is shouldn't. When there is a scrollbar we need to take this
			// into account.
			if ( ie67 && (table.find('tbody').height() > divBodyEl.offsetHeight ||
				divBody.css('overflow-y') == "scroll")
			) {
				tableStyle.width = _fnStringToCss( table.outerWidth() - barWidth);
			}
	
			// Recalculate the sanity width
			sanityWidth = table.outerWidth();
		}
		else if ( scrollXInner !== "" ) {
			// legacy x scroll inner has been given - use it
			tableStyle.width = _fnStringToCss(scrollXInner);
	
			// Recalculate the sanity width
			sanityWidth = table.outerWidth();
		}
	
		// Hidden header should have zero height, so remove padding and borders. Then
		// set the width based on the real headers
	
		// Apply all styles in one pass
		_fnApplyToChildren( zeroOut, headerSrcEls );
	
		// Read all widths in next pass
		_fnApplyToChildren( function(nSizer) {
			headerContent.push( nSizer.innerHTML );
			headerWidths.push( _fnStringToCss( $(nSizer).css('width') ) );
		}, headerSrcEls );
	
		// Apply all widths in final pass
		_fnApplyToChildren( function(nToSize, i) {
			// Only apply widths to the DataTables detected header cells - this
			// prevents complex headers from having contradictory sizes applied
			if ( $.inArray( nToSize, dtHeaderCells ) !== -1 ) {
				nToSize.style.width = headerWidths[i];
			}
		}, headerTrgEls );
	
		$(headerSrcEls).height(0);
	
		/* Same again with the footer if we have one */
		if ( footer )
		{
			_fnApplyToChildren( zeroOut, footerSrcEls );
	
			_fnApplyToChildren( function(nSizer) {
				footerContent.push( nSizer.innerHTML );
				footerWidths.push( _fnStringToCss( $(nSizer).css('width') ) );
			}, footerSrcEls );
	
			_fnApplyToChildren( function(nToSize, i) {
				nToSize.style.width = footerWidths[i];
			}, footerTrgEls );
	
			$(footerSrcEls).height(0);
		}
	
	
		/*
		 * 3. Apply the measurements
		 */
	
		// "Hide" the header and footer that we used for the sizing. We need to keep
		// the content of the cell so that the width applied to the header and body
		// both match, but we want to hide it completely. We want to also fix their
		// width to what they currently are
		_fnApplyToChildren( function(nSizer, i) {
			nSizer.innerHTML = '<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+headerContent[i]+'</div>';
			nSizer.style.width = headerWidths[i];
		}, headerSrcEls );
	
		if ( footer )
		{
			_fnApplyToChildren( function(nSizer, i) {
				nSizer.innerHTML = '<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+footerContent[i]+'</div>';
				nSizer.style.width = footerWidths[i];
			}, footerSrcEls );
		}
	
		// Sanity check that the table is of a sensible width. If not then we are going to get
		// misalignment - try to prevent this by not allowing the table to shrink below its min width
		if ( table.outerWidth() < sanityWidth )
		{
			// The min width depends upon if we have a vertical scrollbar visible or not */
			correction = ((divBodyEl.scrollHeight > divBodyEl.offsetHeight ||
				divBody.css('overflow-y') == "scroll")) ?
					sanityWidth+barWidth :
					sanityWidth;
	
			// IE6/7 are a law unto themselves...
			if ( ie67 && (divBodyEl.scrollHeight >
				divBodyEl.offsetHeight || divBody.css('overflow-y') == "scroll")
			) {
				tableStyle.width = _fnStringToCss( correction-barWidth );
			}
	
			// And give the user a warning that we've stopped the table getting too small
			if ( scrollX === "" || scrollXInner !== "" ) {
				_fnLog( settings, 1, 'Possible column misalignment', 6 );
			}
		}
		else
		{
			correction = '100%';
		}
	
		// Apply to the container elements
		divBodyStyle.width = _fnStringToCss( correction );
		divHeaderStyle.width = _fnStringToCss( correction );
	
		if ( footer ) {
			settings.nScrollFoot.style.width = _fnStringToCss( correction );
		}
	
	
		/*
		 * 4. Clean up
		 */
		if ( ! scrollY ) {
			/* IE7< puts a vertical scrollbar in place (when it shouldn't be) due to subtracting
			 * the scrollbar height from the visible display, rather than adding it on. We need to
			 * set the height in order to sort this. Don't want to do it in any other browsers.
			 */
			if ( ie67 ) {
				divBodyStyle.height = _fnStringToCss( tableEl.offsetHeight+barWidth );
			}
		}
	
		/* Finally set the width's of the header and footer tables */
		var iOuterWidth = table.outerWidth();
		divHeaderTable[0].style.width = _fnStringToCss( iOuterWidth );
		divHeaderInnerStyle.width = _fnStringToCss( iOuterWidth );
	
		// Figure out if there are scrollbar present - if so then we need a the header and footer to
		// provide a bit more space to allow "overflow" scrolling (i.e. past the scrollbar)
		var bScrolling = table.height() > divBodyEl.clientHeight || divBody.css('overflow-y') == "scroll";
		var padding = 'padding' + (browser.bScrollbarLeft ? 'Left' : 'Right' );
		divHeaderInnerStyle[ padding ] = bScrolling ? barWidth+"px" : "0px";
	
		if ( footer ) {
			divFooterTable[0].style.width = _fnStringToCss( iOuterWidth );
			divFooterInner[0].style.width = _fnStringToCss( iOuterWidth );
			divFooterInner[0].style[padding] = bScrolling ? barWidth+"px" : "0px";
		}
	
		// Correct DOM ordering for colgroup - comes before the thead
		table.children('colgroup').insertBefore( table.children('thead') );
	
		/* Adjust the position of the header in case we loose the y-scrollbar */
		divBody.scroll();
	
		// If sorting or filtering has occurred, jump the scrolling back to the top
		// only if we aren't holding the position
		if ( (settings.bSorted || settings.bFiltered) && ! settings._drawHold ) {
			divBodyEl.scrollTop = 0;
		}
	}
	
	
	
	/**
	 * Apply a given function to the display child nodes of an element array (typically
	 * TD children of TR rows
	 *  @param {function} fn Method to apply to the objects
	 *  @param array {nodes} an1 List of elements to look through for display children
	 *  @param array {nodes} an2 Another list (identical structure to the first) - optional
	 *  @memberof DataTable#oApi
	 */
	function _fnApplyToChildren( fn, an1, an2 )
	{
		var index=0, i=0, iLen=an1.length;
		var nNode1, nNode2;
	
		while ( i < iLen ) {
			nNode1 = an1[i].firstChild;
			nNode2 = an2 ? an2[i].firstChild : null;
	
			while ( nNode1 ) {
				if ( nNode1.nodeType === 1 ) {
					if ( an2 ) {
						fn( nNode1, nNode2, index );
					}
					else {
						fn( nNode1, index );
					}
	
					index++;
				}
	
				nNode1 = nNode1.nextSibling;
				nNode2 = an2 ? nNode2.nextSibling : null;
			}
	
			i++;
		}
	}
	
	
	
	var __re_html_remove = /<.*?>/g;
	
	
	/**
	 * Calculate the width of columns for the table
	 *  @param {object} oSettings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnCalculateColumnWidths ( oSettings )
	{
		var
			table = oSettings.nTable,
			columns = oSettings.aoColumns,
			scroll = oSettings.oScroll,
			scrollY = scroll.sY,
			scrollX = scroll.sX,
			scrollXInner = scroll.sXInner,
			columnCount = columns.length,
			visibleColumns = _fnGetColumns( oSettings, 'bVisible' ),
			headerCells = $('th', oSettings.nTHead),
			tableWidthAttr = table.getAttribute('width'), // from DOM element
			tableContainer = table.parentNode,
			userInputs = false,
			i, column, columnIdx, width, outerWidth,
			browser = oSettings.oBrowser,
			ie67 = browser.bScrollOversize;
	
		var styleWidth = table.style.width;
		if ( styleWidth && styleWidth.indexOf('%') !== -1 ) {
			tableWidthAttr = styleWidth;
		}
	
		/* Convert any user input sizes into pixel sizes */
		for ( i=0 ; i<visibleColumns.length ; i++ ) {
			column = columns[ visibleColumns[i] ];
	
			if ( column.sWidth !== null ) {
				column.sWidth = _fnConvertToWidth( column.sWidthOrig, tableContainer );
	
				userInputs = true;
			}
		}
	
		/* If the number of columns in the DOM equals the number that we have to
		 * process in DataTables, then we can use the offsets that are created by
		 * the web- browser. No custom sizes can be set in order for this to happen,
		 * nor scrolling used
		 */
		if ( ie67 || ! userInputs && ! scrollX && ! scrollY &&
		     columnCount == _fnVisbleColumns( oSettings ) &&
		     columnCount == headerCells.length
		) {
			for ( i=0 ; i<columnCount ; i++ ) {
				var colIdx = _fnVisibleToColumnIndex( oSettings, i );
	
				if ( colIdx !== null ) {
					columns[ colIdx ].sWidth = _fnStringToCss( headerCells.eq(i).width() );
				}
			}
		}
		else
		{
			// Otherwise construct a single row, worst case, table with the widest
			// node in the data, assign any user defined widths, then insert it into
			// the DOM and allow the browser to do all the hard work of calculating
			// table widths
			var tmpTable = $(table).clone() // don't use cloneNode - IE8 will remove events on the main table
				.css( 'visibility', 'hidden' )
				.removeAttr( 'id' );
	
			// Clean up the table body
			tmpTable.find('tbody tr').remove();
			var tr = $('<tr/>').appendTo( tmpTable.find('tbody') );
	
			// Clone the table header and footer - we can't use the header / footer
			// from the cloned table, since if scrolling is active, the table's
			// real header and footer are contained in different table tags
			tmpTable.find('thead, tfoot').remove();
			tmpTable
				.append( $(oSettings.nTHead).clone() )
				.append( $(oSettings.nTFoot).clone() );
	
			// Remove any assigned widths from the footer (from scrolling)
			tmpTable.find('tfoot th, tfoot td').css('width', '');
	
			// Apply custom sizing to the cloned header
			headerCells = _fnGetUniqueThs( oSettings, tmpTable.find('thead')[0] );
	
			for ( i=0 ; i<visibleColumns.length ; i++ ) {
				column = columns[ visibleColumns[i] ];
	
				headerCells[i].style.width = column.sWidthOrig !== null && column.sWidthOrig !== '' ?
					_fnStringToCss( column.sWidthOrig ) :
					'';
	
				// For scrollX we need to force the column width otherwise the
				// browser will collapse it. If this width is smaller than the
				// width the column requires, then it will have no effect
				if ( column.sWidthOrig && scrollX ) {
					$( headerCells[i] ).append( $('<div/>').css( {
						width: column.sWidthOrig,
						margin: 0,
						padding: 0,
						border: 0,
						height: 1
					} ) );
				}
			}
	
			// Find the widest cell for each column and put it into the table
			if ( oSettings.aoData.length ) {
				for ( i=0 ; i<visibleColumns.length ; i++ ) {
					columnIdx = visibleColumns[i];
					column = columns[ columnIdx ];
	
					$( _fnGetWidestNode( oSettings, columnIdx ) )
						.clone( false )
						.append( column.sContentPadding )
						.appendTo( tr );
				}
			}
	
			// Tidy the temporary table - remove name attributes so there aren't
			// duplicated in the dom (radio elements for example)
			$('[name]', tmpTable).removeAttr('name');
	
			// Table has been built, attach to the document so we can work with it.
			// A holding element is used, positioned at the top of the container
			// with minimal height, so it has no effect on if the container scrolls
			// or not. Otherwise it might trigger scrolling when it actually isn't
			// needed
			var holder = $('<div/>').css( scrollX || scrollY ?
					{
						position: 'absolute',
						top: 0,
						left: 0,
						height: 1,
						right: 0,
						overflow: 'hidden'
					} :
					{}
				)
				.append( tmpTable )
				.appendTo( tableContainer );
	
			// When scrolling (X or Y) we want to set the width of the table as 
			// appropriate. However, when not scrolling leave the table width as it
			// is. This results in slightly different, but I think correct behaviour
			if ( scrollX && scrollXInner ) {
				tmpTable.width( scrollXInner );
			}
			else if ( scrollX ) {
				tmpTable.css( 'width', 'auto' );
				tmpTable.removeAttr('width');
	
				// If there is no width attribute or style, then allow the table to
				// collapse
				if ( tmpTable.width() < tableContainer.clientWidth && tableWidthAttr ) {
					tmpTable.width( tableContainer.clientWidth );
				}
			}
			else if ( scrollY ) {
				tmpTable.width( tableContainer.clientWidth );
			}
			else if ( tableWidthAttr ) {
				tmpTable.width( tableWidthAttr );
			}
	
			// Get the width of each column in the constructed table - we need to
			// know the inner width (so it can be assigned to the other table's
			// cells) and the outer width so we can calculate the full width of the
			// table. This is safe since DataTables requires a unique cell for each
			// column, but if ever a header can span multiple columns, this will
			// need to be modified.
			var total = 0;
			for ( i=0 ; i<visibleColumns.length ; i++ ) {
				var cell = $(headerCells[i]);
				var border = cell.outerWidth() - cell.width();
	
				// Use getBounding... where possible (not IE8-) because it can give
				// sub-pixel accuracy, which we then want to round up!
				var bounding = browser.bBounding ?
					Math.ceil( headerCells[i].getBoundingClientRect().width ) :
					cell.outerWidth();
	
				// Total is tracked to remove any sub-pixel errors as the outerWidth
				// of the table might not equal the total given here (IE!).
				total += bounding;
	
				// Width for each column to use
				columns[ visibleColumns[i] ].sWidth = _fnStringToCss( bounding - border );
			}
	
			table.style.width = _fnStringToCss( total );
	
			// Finished with the table - ditch it
			holder.remove();
		}
	
		// If there is a width attr, we want to attach an event listener which
		// allows the table sizing to automatically adjust when the window is
		// resized. Use the width attr rather than CSS, since we can't know if the
		// CSS is a relative value or absolute - DOM read is always px.
		if ( tableWidthAttr ) {
			table.style.width = _fnStringToCss( tableWidthAttr );
		}
	
		if ( (tableWidthAttr || scrollX) && ! oSettings._reszEvt ) {
			var bindResize = function () {
				$(window).bind('resize.DT-'+oSettings.sInstance, _fnThrottle( function () {
					_fnAdjustColumnSizing( oSettings );
				} ) );
			};
	
			// IE6/7 will crash if we bind a resize event handler on page load.
			// To be removed in 1.11 which drops IE6/7 support
			if ( ie67 ) {
				setTimeout( bindResize, 1000 );
			}
			else {
				bindResize();
			}
	
			oSettings._reszEvt = true;
		}
	}
	
	
	/**
	 * Throttle the calls to a function. Arguments and context are maintained for
	 * the throttled function
	 *  @param {function} fn Function to be called
	 *  @param {int} [freq=200] call frequency in mS
	 *  @returns {function} wrapped function
	 *  @memberof DataTable#oApi
	 */
	var _fnThrottle = DataTable.util.throttle;
	
	
	/**
	 * Convert a CSS unit width to pixels (e.g. 2em)
	 *  @param {string} width width to be converted
	 *  @param {node} parent parent to get the with for (required for relative widths) - optional
	 *  @returns {int} width in pixels
	 *  @memberof DataTable#oApi
	 */
	function _fnConvertToWidth ( width, parent )
	{
		if ( ! width ) {
			return 0;
		}
	
		var n = $('<div/>')
			.css( 'width', _fnStringToCss( width ) )
			.appendTo( parent || document.body );
	
		var val = n[0].offsetWidth;
		n.remove();
	
		return val;
	}
	
	
	/**
	 * Get the widest node
	 *  @param {object} settings dataTables settings object
	 *  @param {int} colIdx column of interest
	 *  @returns {node} widest table node
	 *  @memberof DataTable#oApi
	 */
	function _fnGetWidestNode( settings, colIdx )
	{
		var idx = _fnGetMaxLenString( settings, colIdx );
		if ( idx < 0 ) {
			return null;
		}
	
		var data = settings.aoData[ idx ];
		return ! data.nTr ? // Might not have been created when deferred rendering
			$('<td/>').html( _fnGetCellData( settings, idx, colIdx, 'display' ) )[0] :
			data.anCells[ colIdx ];
	}
	
	
	/**
	 * Get the maximum strlen for each data column
	 *  @param {object} settings dataTables settings object
	 *  @param {int} colIdx column of interest
	 *  @returns {string} max string length for each column
	 *  @memberof DataTable#oApi
	 */
	function _fnGetMaxLenString( settings, colIdx )
	{
		var s, max=-1, maxIdx = -1;
	
		for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
			s = _fnGetCellData( settings, i, colIdx, 'display' )+'';
			s = s.replace( __re_html_remove, '' );
			s = s.replace( /&nbsp;/g, ' ' );
	
			if ( s.length > max ) {
				max = s.length;
				maxIdx = i;
			}
		}
	
		return maxIdx;
	}
	
	
	/**
	 * Append a CSS unit (only if required) to a string
	 *  @param {string} value to css-ify
	 *  @returns {string} value with css unit
	 *  @memberof DataTable#oApi
	 */
	function _fnStringToCss( s )
	{
		if ( s === null ) {
			return '0px';
		}
	
		if ( typeof s == 'number' ) {
			return s < 0 ?
				'0px' :
				s+'px';
		}
	
		// Check it has a unit character already
		return s.match(/\d$/) ?
			s+'px' :
			s;
	}
	
	
	
	function _fnSortFlatten ( settings )
	{
		var
			i, iLen, k, kLen,
			aSort = [],
			aiOrig = [],
			aoColumns = settings.aoColumns,
			aDataSort, iCol, sType, srcCol,
			fixed = settings.aaSortingFixed,
			fixedObj = $.isPlainObject( fixed ),
			nestedSort = [],
			add = function ( a ) {
				if ( a.length && ! $.isArray( a[0] ) ) {
					// 1D array
					nestedSort.push( a );
				}
				else {
					// 2D array
					$.merge( nestedSort, a );
				}
			};
	
		// Build the sort array, with pre-fix and post-fix options if they have been
		// specified
		if ( $.isArray( fixed ) ) {
			add( fixed );
		}
	
		if ( fixedObj && fixed.pre ) {
			add( fixed.pre );
		}
	
		add( settings.aaSorting );
	
		if (fixedObj && fixed.post ) {
			add( fixed.post );
		}
	
		for ( i=0 ; i<nestedSort.length ; i++ )
		{
			srcCol = nestedSort[i][0];
			aDataSort = aoColumns[ srcCol ].aDataSort;
	
			for ( k=0, kLen=aDataSort.length ; k<kLen ; k++ )
			{
				iCol = aDataSort[k];
				sType = aoColumns[ iCol ].sType || 'string';
	
				if ( nestedSort[i]._idx === undefined ) {
					nestedSort[i]._idx = $.inArray( nestedSort[i][1], aoColumns[iCol].asSorting );
				}
	
				aSort.push( {
					src:       srcCol,
					col:       iCol,
					dir:       nestedSort[i][1],
					index:     nestedSort[i]._idx,
					type:      sType,
					formatter: DataTable.ext.type.order[ sType+"-pre" ]
				} );
			}
		}
	
		return aSort;
	}
	
	/**
	 * Change the order of the table
	 *  @param {object} oSettings dataTables settings object
	 *  @memberof DataTable#oApi
	 *  @todo This really needs split up!
	 */
	function _fnSort ( oSettings )
	{
		var
			i, ien, iLen, j, jLen, k, kLen,
			sDataType, nTh,
			aiOrig = [],
			oExtSort = DataTable.ext.type.order,
			aoData = oSettings.aoData,
			aoColumns = oSettings.aoColumns,
			aDataSort, data, iCol, sType, oSort,
			formatters = 0,
			sortCol,
			displayMaster = oSettings.aiDisplayMaster,
			aSort;
	
		// Resolve any column types that are unknown due to addition or invalidation
		// @todo Can this be moved into a 'data-ready' handler which is called when
		//   data is going to be used in the table?
		_fnColumnTypes( oSettings );
	
		aSort = _fnSortFlatten( oSettings );
	
		for ( i=0, ien=aSort.length ; i<ien ; i++ ) {
			sortCol = aSort[i];
	
			// Track if we can use the fast sort algorithm
			if ( sortCol.formatter ) {
				formatters++;
			}
	
			// Load the data needed for the sort, for each cell
			_fnSortData( oSettings, sortCol.col );
		}
	
		/* No sorting required if server-side or no sorting array */
		if ( _fnDataSource( oSettings ) != 'ssp' && aSort.length !== 0 )
		{
			// Create a value - key array of the current row positions such that we can use their
			// current position during the sort, if values match, in order to perform stable sorting
			for ( i=0, iLen=displayMaster.length ; i<iLen ; i++ ) {
				aiOrig[ displayMaster[i] ] = i;
			}
	
			/* Do the sort - here we want multi-column sorting based on a given data source (column)
			 * and sorting function (from oSort) in a certain direction. It's reasonably complex to
			 * follow on it's own, but this is what we want (example two column sorting):
			 *  fnLocalSorting = function(a,b){
			 *    var iTest;
			 *    iTest = oSort['string-asc']('data11', 'data12');
			 *      if (iTest !== 0)
			 *        return iTest;
			 *    iTest = oSort['numeric-desc']('data21', 'data22');
			 *    if (iTest !== 0)
			 *      return iTest;
			 *    return oSort['numeric-asc']( aiOrig[a], aiOrig[b] );
			 *  }
			 * Basically we have a test for each sorting column, if the data in that column is equal,
			 * test the next column. If all columns match, then we use a numeric sort on the row
			 * positions in the original data array to provide a stable sort.
			 *
			 * Note - I know it seems excessive to have two sorting methods, but the first is around
			 * 15% faster, so the second is only maintained for backwards compatibility with sorting
			 * methods which do not have a pre-sort formatting function.
			 */
			if ( formatters === aSort.length ) {
				// All sort types have formatting functions
				displayMaster.sort( function ( a, b ) {
					var
						x, y, k, test, sort,
						len=aSort.length,
						dataA = aoData[a]._aSortData,
						dataB = aoData[b]._aSortData;
	
					for ( k=0 ; k<len ; k++ ) {
						sort = aSort[k];
	
						x = dataA[ sort.col ];
						y = dataB[ sort.col ];
	
						test = x<y ? -1 : x>y ? 1 : 0;
						if ( test !== 0 ) {
							return sort.dir === 'asc' ? test : -test;
						}
					}
	
					x = aiOrig[a];
					y = aiOrig[b];
					return x<y ? -1 : x>y ? 1 : 0;
				} );
			}
			else {
				// Depreciated - remove in 1.11 (providing a plug-in option)
				// Not all sort types have formatting methods, so we have to call their sorting
				// methods.
				displayMaster.sort( function ( a, b ) {
					var
						x, y, k, l, test, sort, fn,
						len=aSort.length,
						dataA = aoData[a]._aSortData,
						dataB = aoData[b]._aSortData;
	
					for ( k=0 ; k<len ; k++ ) {
						sort = aSort[k];
	
						x = dataA[ sort.col ];
						y = dataB[ sort.col ];
	
						fn = oExtSort[ sort.type+"-"+sort.dir ] || oExtSort[ "string-"+sort.dir ];
						test = fn( x, y );
						if ( test !== 0 ) {
							return test;
						}
					}
	
					x = aiOrig[a];
					y = aiOrig[b];
					return x<y ? -1 : x>y ? 1 : 0;
				} );
			}
		}
	
		/* Tell the draw function that we have sorted the data */
		oSettings.bSorted = true;
	}
	
	
	function _fnSortAria ( settings )
	{
		var label;
		var nextSort;
		var columns = settings.aoColumns;
		var aSort = _fnSortFlatten( settings );
		var oAria = settings.oLanguage.oAria;
	
		// ARIA attributes - need to loop all columns, to update all (removing old
		// attributes as needed)
		for ( var i=0, iLen=columns.length ; i<iLen ; i++ )
		{
			var col = columns[i];
			var asSorting = col.asSorting;
			var sTitle = col.sTitle.replace( /<.*?>/g, "" );
			var th = col.nTh;
	
			// IE7 is throwing an error when setting these properties with jQuery's
			// attr() and removeAttr() methods...
			th.removeAttribute('aria-sort');
	
			/* In ARIA only the first sorting column can be marked as sorting - no multi-sort option */
			if ( col.bSortable ) {
				if ( aSort.length > 0 && aSort[0].col == i ) {
					th.setAttribute('aria-sort', aSort[0].dir=="asc" ? "ascending" : "descending" );
					nextSort = asSorting[ aSort[0].index+1 ] || asSorting[0];
				}
				else {
					nextSort = asSorting[0];
				}
	
				label = sTitle + ( nextSort === "asc" ?
					oAria.sSortAscending :
					oAria.sSortDescending
				);
			}
			else {
				label = sTitle;
			}
	
			th.setAttribute('aria-label', label);
		}
	}
	
	
	/**
	 * Function to run on user sort request
	 *  @param {object} settings dataTables settings object
	 *  @param {node} attachTo node to attach the handler to
	 *  @param {int} colIdx column sorting index
	 *  @param {boolean} [append=false] Append the requested sort to the existing
	 *    sort if true (i.e. multi-column sort)
	 *  @param {function} [callback] callback function
	 *  @memberof DataTable#oApi
	 */
	function _fnSortListener ( settings, colIdx, append, callback )
	{
		var col = settings.aoColumns[ colIdx ];
		var sorting = settings.aaSorting;
		var asSorting = col.asSorting;
		var nextSortIdx;
		var next = function ( a, overflow ) {
			var idx = a._idx;
			if ( idx === undefined ) {
				idx = $.inArray( a[1], asSorting );
			}
	
			return idx+1 < asSorting.length ?
				idx+1 :
				overflow ?
					null :
					0;
		};
	
		// Convert to 2D array if needed
		if ( typeof sorting[0] === 'number' ) {
			sorting = settings.aaSorting = [ sorting ];
		}
	
		// If appending the sort then we are multi-column sorting
		if ( append && settings.oFeatures.bSortMulti ) {
			// Are we already doing some kind of sort on this column?
			var sortIdx = $.inArray( colIdx, _pluck(sorting, '0') );
	
			if ( sortIdx !== -1 ) {
				// Yes, modify the sort
				nextSortIdx = next( sorting[sortIdx], true );
	
				if ( nextSortIdx === null && sorting.length === 1 ) {
					nextSortIdx = 0; // can't remove sorting completely
				}
	
				if ( nextSortIdx === null ) {
					sorting.splice( sortIdx, 1 );
				}
				else {
					sorting[sortIdx][1] = asSorting[ nextSortIdx ];
					sorting[sortIdx]._idx = nextSortIdx;
				}
			}
			else {
				// No sort on this column yet
				sorting.push( [ colIdx, asSorting[0], 0 ] );
				sorting[sorting.length-1]._idx = 0;
			}
		}
		else if ( sorting.length && sorting[0][0] == colIdx ) {
			// Single column - already sorting on this column, modify the sort
			nextSortIdx = next( sorting[0] );
	
			sorting.length = 1;
			sorting[0][1] = asSorting[ nextSortIdx ];
			sorting[0]._idx = nextSortIdx;
		}
		else {
			// Single column - sort only on this column
			sorting.length = 0;
			sorting.push( [ colIdx, asSorting[0] ] );
			sorting[0]._idx = 0;
		}
	
		// Run the sort by calling a full redraw
		_fnReDraw( settings );
	
		// callback used for async user interaction
		if ( typeof callback == 'function' ) {
			callback( settings );
		}
	}
	
	
	/**
	 * Attach a sort handler (click) to a node
	 *  @param {object} settings dataTables settings object
	 *  @param {node} attachTo node to attach the handler to
	 *  @param {int} colIdx column sorting index
	 *  @param {function} [callback] callback function
	 *  @memberof DataTable#oApi
	 */
	function _fnSortAttachListener ( settings, attachTo, colIdx, callback )
	{
		var col = settings.aoColumns[ colIdx ];
	
		_fnBindAction( attachTo, {}, function (e) {
			/* If the column is not sortable - don't to anything */
			if ( col.bSortable === false ) {
				return;
			}
	
			// If processing is enabled use a timeout to allow the processing
			// display to be shown - otherwise to it synchronously
			if ( settings.oFeatures.bProcessing ) {
				_fnProcessingDisplay( settings, true );
	
				setTimeout( function() {
					_fnSortListener( settings, colIdx, e.shiftKey, callback );
	
					// In server-side processing, the draw callback will remove the
					// processing display
					if ( _fnDataSource( settings ) !== 'ssp' ) {
						_fnProcessingDisplay( settings, false );
					}
				}, 0 );
			}
			else {
				_fnSortListener( settings, colIdx, e.shiftKey, callback );
			}
		} );
	}
	
	
	/**
	 * Set the sorting classes on table's body, Note: it is safe to call this function
	 * when bSort and bSortClasses are false
	 *  @param {object} oSettings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnSortingClasses( settings )
	{
		var oldSort = settings.aLastSort;
		var sortClass = settings.oClasses.sSortColumn;
		var sort = _fnSortFlatten( settings );
		var features = settings.oFeatures;
		var i, ien, colIdx;
	
		if ( features.bSort && features.bSortClasses ) {
			// Remove old sorting classes
			for ( i=0, ien=oldSort.length ; i<ien ; i++ ) {
				colIdx = oldSort[i].src;
	
				// Remove column sorting
				$( _pluck( settings.aoData, 'anCells', colIdx ) )
					.removeClass( sortClass + (i<2 ? i+1 : 3) );
			}
	
			// Add new column sorting
			for ( i=0, ien=sort.length ; i<ien ; i++ ) {
				colIdx = sort[i].src;
	
				$( _pluck( settings.aoData, 'anCells', colIdx ) )
					.addClass( sortClass + (i<2 ? i+1 : 3) );
			}
		}
	
		settings.aLastSort = sort;
	}
	
	
	// Get the data to sort a column, be it from cache, fresh (populating the
	// cache), or from a sort formatter
	function _fnSortData( settings, idx )
	{
		// Custom sorting function - provided by the sort data type
		var column = settings.aoColumns[ idx ];
		var customSort = DataTable.ext.order[ column.sSortDataType ];
		var customData;
	
		if ( customSort ) {
			customData = customSort.call( settings.oInstance, settings, idx,
				_fnColumnIndexToVisible( settings, idx )
			);
		}
	
		// Use / populate cache
		var row, cellData;
		var formatter = DataTable.ext.type.order[ column.sType+"-pre" ];
	
		for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
			row = settings.aoData[i];
	
			if ( ! row._aSortData ) {
				row._aSortData = [];
			}
	
			if ( ! row._aSortData[idx] || customSort ) {
				cellData = customSort ?
					customData[i] : // If there was a custom sort function, use data from there
					_fnGetCellData( settings, i, idx, 'sort' );
	
				row._aSortData[ idx ] = formatter ?
					formatter( cellData ) :
					cellData;
			}
		}
	}
	
	
	
	/**
	 * Save the state of a table
	 *  @param {object} oSettings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnSaveState ( settings )
	{
		if ( !settings.oFeatures.bStateSave || settings.bDestroying )
		{
			return;
		}
	
		/* Store the interesting variables */
		var state = {
			time:    +new Date(),
			start:   settings._iDisplayStart,
			length:  settings._iDisplayLength,
			order:   $.extend( true, [], settings.aaSorting ),
			search:  _fnSearchToCamel( settings.oPreviousSearch ),
			columns: $.map( settings.aoColumns, function ( col, i ) {
				return {
					visible: col.bVisible,
					search: _fnSearchToCamel( settings.aoPreSearchCols[i] )
				};
			} )
		};
	
		_fnCallbackFire( settings, "aoStateSaveParams", 'stateSaveParams', [settings, state] );
	
		settings.oSavedState = state;
		settings.fnStateSaveCallback.call( settings.oInstance, settings, state );
	}
	
	
	/**
	 * Attempt to load a saved table state
	 *  @param {object} oSettings dataTables settings object
	 *  @param {object} oInit DataTables init object so we can override settings
	 *  @memberof DataTable#oApi
	 */
	function _fnLoadState ( settings, oInit )
	{
		var i, ien;
		var columns = settings.aoColumns;
	
		if ( ! settings.oFeatures.bStateSave ) {
			return;
		}
	
		var state = settings.fnStateLoadCallback.call( settings.oInstance, settings );
		if ( ! state || ! state.time ) {
			return;
		}
	
		/* Allow custom and plug-in manipulation functions to alter the saved data set and
		 * cancelling of loading by returning false
		 */
		var abStateLoad = _fnCallbackFire( settings, 'aoStateLoadParams', 'stateLoadParams', [settings, state] );
		if ( $.inArray( false, abStateLoad ) !== -1 ) {
			return;
		}
	
		/* Reject old data */
		var duration = settings.iStateDuration;
		if ( duration > 0 && state.time < +new Date() - (duration*1000) ) {
			return;
		}
	
		// Number of columns have changed - all bets are off, no restore of settings
		if ( columns.length !== state.columns.length ) {
			return;
		}
	
		// Store the saved state so it might be accessed at any time
		settings.oLoadedState = $.extend( true, {}, state );
	
		// Restore key features - todo - for 1.11 this needs to be done by
		// subscribed events
		if ( state.start !== undefined ) {
			settings._iDisplayStart    = state.start;
			settings.iInitDisplayStart = state.start;
		}
		if ( state.length !== undefined ) {
			settings._iDisplayLength   = state.length;
		}
	
		// Order
		if ( state.order !== undefined ) {
			settings.aaSorting = [];
			$.each( state.order, function ( i, col ) {
				settings.aaSorting.push( col[0] >= columns.length ?
					[ 0, col[1] ] :
					col
				);
			} );
		}
	
		// Search
		if ( state.search !== undefined ) {
			$.extend( settings.oPreviousSearch, _fnSearchToHung( state.search ) );
		}
	
		// Columns
		for ( i=0, ien=state.columns.length ; i<ien ; i++ ) {
			var col = state.columns[i];
	
			// Visibility
			if ( col.visible !== undefined ) {
				columns[i].bVisible = col.visible;
			}
	
			// Search
			if ( col.search !== undefined ) {
				$.extend( settings.aoPreSearchCols[i], _fnSearchToHung( col.search ) );
			}
		}
	
		_fnCallbackFire( settings, 'aoStateLoaded', 'stateLoaded', [settings, state] );
	}
	
	
	/**
	 * Return the settings object for a particular table
	 *  @param {node} table table we are using as a dataTable
	 *  @returns {object} Settings object - or null if not found
	 *  @memberof DataTable#oApi
	 */
	function _fnSettingsFromNode ( table )
	{
		var settings = DataTable.settings;
		var idx = $.inArray( table, _pluck( settings, 'nTable' ) );
	
		return idx !== -1 ?
			settings[ idx ] :
			null;
	}
	
	
	/**
	 * Log an error message
	 *  @param {object} settings dataTables settings object
	 *  @param {int} level log error messages, or display them to the user
	 *  @param {string} msg error message
	 *  @param {int} tn Technical note id to get more information about the error.
	 *  @memberof DataTable#oApi
	 */
	function _fnLog( settings, level, msg, tn )
	{
		msg = 'DataTables warning: '+
			(settings ? 'table id='+settings.sTableId+' - ' : '')+msg;
	
		if ( tn ) {
			msg += '. For more information about this error, please see '+
			'http://datatables.net/tn/'+tn;
		}
	
		if ( ! level  ) {
			// Backwards compatibility pre 1.10
			var ext = DataTable.ext;
			var type = ext.sErrMode || ext.errMode;
	
			if ( settings ) {
				_fnCallbackFire( settings, null, 'error', [ settings, tn, msg ] );
			}
	
			if ( type == 'alert' ) {
				alert( msg );
			}
			else if ( type == 'throw' ) {
				throw new Error(msg);
			}
			else if ( typeof type == 'function' ) {
				type( settings, tn, msg );
			}
		}
		else if ( window.console && console.log ) {
			console.log( msg );
		}
	}
	
	
	/**
	 * See if a property is defined on one object, if so assign it to the other object
	 *  @param {object} ret target object
	 *  @param {object} src source object
	 *  @param {string} name property
	 *  @param {string} [mappedName] name to map too - optional, name used if not given
	 *  @memberof DataTable#oApi
	 */
	function _fnMap( ret, src, name, mappedName )
	{
		if ( $.isArray( name ) ) {
			$.each( name, function (i, val) {
				if ( $.isArray( val ) ) {
					_fnMap( ret, src, val[0], val[1] );
				}
				else {
					_fnMap( ret, src, val );
				}
			} );
	
			return;
		}
	
		if ( mappedName === undefined ) {
			mappedName = name;
		}
	
		if ( src[name] !== undefined ) {
			ret[mappedName] = src[name];
		}
	}
	
	
	/**
	 * Extend objects - very similar to jQuery.extend, but deep copy objects, and
	 * shallow copy arrays. The reason we need to do this, is that we don't want to
	 * deep copy array init values (such as aaSorting) since the dev wouldn't be
	 * able to override them, but we do want to deep copy arrays.
	 *  @param {object} out Object to extend
	 *  @param {object} extender Object from which the properties will be applied to
	 *      out
	 *  @param {boolean} breakRefs If true, then arrays will be sliced to take an
	 *      independent copy with the exception of the `data` or `aaData` parameters
	 *      if they are present. This is so you can pass in a collection to
	 *      DataTables and have that used as your data source without breaking the
	 *      references
	 *  @returns {object} out Reference, just for convenience - out === the return.
	 *  @memberof DataTable#oApi
	 *  @todo This doesn't take account of arrays inside the deep copied objects.
	 */
	function _fnExtend( out, extender, breakRefs )
	{
		var val;
	
		for ( var prop in extender ) {
			if ( extender.hasOwnProperty(prop) ) {
				val = extender[prop];
	
				if ( $.isPlainObject( val ) ) {
					if ( ! $.isPlainObject( out[prop] ) ) {
						out[prop] = {};
					}
					$.extend( true, out[prop], val );
				}
				else if ( breakRefs && prop !== 'data' && prop !== 'aaData' && $.isArray(val) ) {
					out[prop] = val.slice();
				}
				else {
					out[prop] = val;
				}
			}
		}
	
		return out;
	}
	
	
	/**
	 * Bind an event handers to allow a click or return key to activate the callback.
	 * This is good for accessibility since a return on the keyboard will have the
	 * same effect as a click, if the element has focus.
	 *  @param {element} n Element to bind the action to
	 *  @param {object} oData Data object to pass to the triggered function
	 *  @param {function} fn Callback function for when the event is triggered
	 *  @memberof DataTable#oApi
	 */
	function _fnBindAction( n, oData, fn )
	{
		$(n)
			.bind( 'click.DT', oData, function (e) {
					n.blur(); // Remove focus outline for mouse users
					fn(e);
				} )
			.bind( 'keypress.DT', oData, function (e){
					if ( e.which === 13 ) {
						e.preventDefault();
						fn(e);
					}
				} )
			.bind( 'selectstart.DT', function () {
					/* Take the brutal approach to cancelling text selection */
					return false;
				} );
	}
	
	
	/**
	 * Register a callback function. Easily allows a callback function to be added to
	 * an array store of callback functions that can then all be called together.
	 *  @param {object} oSettings dataTables settings object
	 *  @param {string} sStore Name of the array storage for the callbacks in oSettings
	 *  @param {function} fn Function to be called back
	 *  @param {string} sName Identifying name for the callback (i.e. a label)
	 *  @memberof DataTable#oApi
	 */
	function _fnCallbackReg( oSettings, sStore, fn, sName )
	{
		if ( fn )
		{
			oSettings[sStore].push( {
				"fn": fn,
				"sName": sName
			} );
		}
	}
	
	
	/**
	 * Fire callback functions and trigger events. Note that the loop over the
	 * callback array store is done backwards! Further note that you do not want to
	 * fire off triggers in time sensitive applications (for example cell creation)
	 * as its slow.
	 *  @param {object} settings dataTables settings object
	 *  @param {string} callbackArr Name of the array storage for the callbacks in
	 *      oSettings
	 *  @param {string} eventName Name of the jQuery custom event to trigger. If
	 *      null no trigger is fired
	 *  @param {array} args Array of arguments to pass to the callback function /
	 *      trigger
	 *  @memberof DataTable#oApi
	 */
	function _fnCallbackFire( settings, callbackArr, eventName, args )
	{
		var ret = [];
	
		if ( callbackArr ) {
			ret = $.map( settings[callbackArr].slice().reverse(), function (val, i) {
				return val.fn.apply( settings.oInstance, args );
			} );
		}
	
		if ( eventName !== null ) {
			var e = $.Event( eventName+'.dt' );
	
			$(settings.nTable).trigger( e, args );
	
			ret.push( e.result );
		}
	
		return ret;
	}
	
	
	function _fnLengthOverflow ( settings )
	{
		var
			start = settings._iDisplayStart,
			end = settings.fnDisplayEnd(),
			len = settings._iDisplayLength;
	
		/* If we have space to show extra rows (backing up from the end point - then do so */
		if ( start >= end )
		{
			start = end - len;
		}
	
		// Keep the start record on the current page
		start -= (start % len);
	
		if ( len === -1 || start < 0 )
		{
			start = 0;
		}
	
		settings._iDisplayStart = start;
	}
	
	
	function _fnRenderer( settings, type )
	{
		var renderer = settings.renderer;
		var host = DataTable.ext.renderer[type];
	
		if ( $.isPlainObject( renderer ) && renderer[type] ) {
			// Specific renderer for this type. If available use it, otherwise use
			// the default.
			return host[renderer[type]] || host._;
		}
		else if ( typeof renderer === 'string' ) {
			// Common renderer - if there is one available for this type use it,
			// otherwise use the default
			return host[renderer] || host._;
		}
	
		// Use the default
		return host._;
	}
	
	
	/**
	 * Detect the data source being used for the table. Used to simplify the code
	 * a little (ajax) and to make it compress a little smaller.
	 *
	 *  @param {object} settings dataTables settings object
	 *  @returns {string} Data source
	 *  @memberof DataTable#oApi
	 */
	function _fnDataSource ( settings )
	{
		if ( settings.oFeatures.bServerSide ) {
			return 'ssp';
		}
		else if ( settings.ajax || settings.sAjaxSource ) {
			return 'ajax';
		}
		return 'dom';
	}
	

	
	
	/**
	 * Computed structure of the DataTables API, defined by the options passed to
	 * `DataTable.Api.register()` when building the API.
	 *
	 * The structure is built in order to speed creation and extension of the Api
	 * objects since the extensions are effectively pre-parsed.
	 *
	 * The array is an array of objects with the following structure, where this
	 * base array represents the Api prototype base:
	 *
	 *     [
	 *       {
	 *         name:      'data'                -- string   - Property name
	 *         val:       function () {},       -- function - Api method (or undefined if just an object
	 *         methodExt: [ ... ],              -- array    - Array of Api object definitions to extend the method result
	 *         propExt:   [ ... ]               -- array    - Array of Api object definitions to extend the property
	 *       },
	 *       {
	 *         name:     'row'
	 *         val:       {},
	 *         methodExt: [ ... ],
	 *         propExt:   [
	 *           {
	 *             name:      'data'
	 *             val:       function () {},
	 *             methodExt: [ ... ],
	 *             propExt:   [ ... ]
	 *           },
	 *           ...
	 *         ]
	 *       }
	 *     ]
	 *
	 * @type {Array}
	 * @ignore
	 */
	var __apiStruct = [];
	
	
	/**
	 * `Array.prototype` reference.
	 *
	 * @type object
	 * @ignore
	 */
	var __arrayProto = Array.prototype;
	
	
	/**
	 * Abstraction for `context` parameter of the `Api` constructor to allow it to
	 * take several different forms for ease of use.
	 *
	 * Each of the input parameter types will be converted to a DataTables settings
	 * object where possible.
	 *
	 * @param  {string|node|jQuery|object} mixed DataTable identifier. Can be one
	 *   of:
	 *
	 *   * `string` - jQuery selector. Any DataTables' matching the given selector
	 *     with be found and used.
	 *   * `node` - `TABLE` node which has already been formed into a DataTable.
	 *   * `jQuery` - A jQuery object of `TABLE` nodes.
	 *   * `object` - DataTables settings object
	 *   * `DataTables.Api` - API instance
	 * @return {array|null} Matching DataTables settings objects. `null` or
	 *   `undefined` is returned if no matching DataTable is found.
	 * @ignore
	 */
	var _toSettings = function ( mixed )
	{
		var idx, jq;
		var settings = DataTable.settings;
		var tables = $.map( settings, function (el, i) {
			return el.nTable;
		} );
	
		if ( ! mixed ) {
			return [];
		}
		else if ( mixed.nTable && mixed.oApi ) {
			// DataTables settings object
			return [ mixed ];
		}
		else if ( mixed.nodeName && mixed.nodeName.toLowerCase() === 'table' ) {
			// Table node
			idx = $.inArray( mixed, tables );
			return idx !== -1 ? [ settings[idx] ] : null;
		}
		else if ( mixed && typeof mixed.settings === 'function' ) {
			return mixed.settings().toArray();
		}
		else if ( typeof mixed === 'string' ) {
			// jQuery selector
			jq = $(mixed);
		}
		else if ( mixed instanceof $ ) {
			// jQuery object (also DataTables instance)
			jq = mixed;
		}
	
		if ( jq ) {
			return jq.map( function(i) {
				idx = $.inArray( this, tables );
				return idx !== -1 ? settings[idx] : null;
			} ).toArray();
		}
	};
	
	
	/**
	 * DataTables API class - used to control and interface with  one or more
	 * DataTables enhanced tables.
	 *
	 * The API class is heavily based on jQuery, presenting a chainable interface
	 * that you can use to interact with tables. Each instance of the API class has
	 * a "context" - i.e. the tables that it will operate on. This could be a single
	 * table, all tables on a page or a sub-set thereof.
	 *
	 * Additionally the API is designed to allow you to easily work with the data in
	 * the tables, retrieving and manipulating it as required. This is done by
	 * presenting the API class as an array like interface. The contents of the
	 * array depend upon the actions requested by each method (for example
	 * `rows().nodes()` will return an array of nodes, while `rows().data()` will
	 * return an array of objects or arrays depending upon your table's
	 * configuration). The API object has a number of array like methods (`push`,
	 * `pop`, `reverse` etc) as well as additional helper methods (`each`, `pluck`,
	 * `unique` etc) to assist your working with the data held in a table.
	 *
	 * Most methods (those which return an Api instance) are chainable, which means
	 * the return from a method call also has all of the methods available that the
	 * top level object had. For example, these two calls are equivalent:
	 *
	 *     // Not chained
	 *     api.row.add( {...} );
	 *     api.draw();
	 *
	 *     // Chained
	 *     api.row.add( {...} ).draw();
	 *
	 * @class DataTable.Api
	 * @param {array|object|string|jQuery} context DataTable identifier. This is
	 *   used to define which DataTables enhanced tables this API will operate on.
	 *   Can be one of:
	 *
	 *   * `string` - jQuery selector. Any DataTables' matching the given selector
	 *     with be found and used.
	 *   * `node` - `TABLE` node which has already been formed into a DataTable.
	 *   * `jQuery` - A jQuery object of `TABLE` nodes.
	 *   * `object` - DataTables settings object
	 * @param {array} [data] Data to initialise the Api instance with.
	 *
	 * @example
	 *   // Direct initialisation during DataTables construction
	 *   var api = $('#example').DataTable();
	 *
	 * @example
	 *   // Initialisation using a DataTables jQuery object
	 *   var api = $('#example').dataTable().api();
	 *
	 * @example
	 *   // Initialisation as a constructor
	 *   var api = new $.fn.DataTable.Api( 'table.dataTable' );
	 */
	_Api = function ( context, data )
	{
		if ( ! (this instanceof _Api) ) {
			return new _Api( context, data );
		}
	
		var settings = [];
		var ctxSettings = function ( o ) {
			var a = _toSettings( o );
			if ( a ) {
				settings = settings.concat( a );
			}
		};
	
		if ( $.isArray( context ) ) {
			for ( var i=0, ien=context.length ; i<ien ; i++ ) {
				ctxSettings( context[i] );
			}
		}
		else {
			ctxSettings( context );
		}
	
		// Remove duplicates
		this.context = _unique( settings );
	
		// Initial data
		if ( data ) {
			$.merge( this, data );
		}
	
		// selector
		this.selector = {
			rows: null,
			cols: null,
			opts: null
		};
	
		_Api.extend( this, this, __apiStruct );
	};
	
	DataTable.Api = _Api;
	
	// Don't destroy the existing prototype, just extend it. Required for jQuery 2's
	// isPlainObject.
	$.extend( _Api.prototype, {
		any: function ()
		{
			return this.count() !== 0;
		},
	
	
		concat:  __arrayProto.concat,
	
	
		context: [], // array of table settings objects
	
	
		count: function ()
		{
			return this.flatten().length;
		},
	
	
		each: function ( fn )
		{
			for ( var i=0, ien=this.length ; i<ien; i++ ) {
				fn.call( this, this[i], i, this );
			}
	
			return this;
		},
	
	
		eq: function ( idx )
		{
			var ctx = this.context;
	
			return ctx.length > idx ?
				new _Api( ctx[idx], this[idx] ) :
				null;
		},
	
	
		filter: function ( fn )
		{
			var a = [];
	
			if ( __arrayProto.filter ) {
				a = __arrayProto.filter.call( this, fn, this );
			}
			else {
				// Compatibility for browsers without EMCA-252-5 (JS 1.6)
				for ( var i=0, ien=this.length ; i<ien ; i++ ) {
					if ( fn.call( this, this[i], i, this ) ) {
						a.push( this[i] );
					}
				}
			}
	
			return new _Api( this.context, a );
		},
	
	
		flatten: function ()
		{
			var a = [];
			return new _Api( this.context, a.concat.apply( a, this.toArray() ) );
		},
	
	
		join:    __arrayProto.join,
	
	
		indexOf: __arrayProto.indexOf || function (obj, start)
		{
			for ( var i=(start || 0), ien=this.length ; i<ien ; i++ ) {
				if ( this[i] === obj ) {
					return i;
				}
			}
			return -1;
		},
	
		iterator: function ( flatten, type, fn, alwaysNew ) {
			var
				a = [], ret,
				i, ien, j, jen,
				context = this.context,
				rows, items, item,
				selector = this.selector;
	
			// Argument shifting
			if ( typeof flatten === 'string' ) {
				alwaysNew = fn;
				fn = type;
				type = flatten;
				flatten = false;
			}
	
			for ( i=0, ien=context.length ; i<ien ; i++ ) {
				var apiInst = new _Api( context[i] );
	
				if ( type === 'table' ) {
					ret = fn.call( apiInst, context[i], i );
	
					if ( ret !== undefined ) {
						a.push( ret );
					}
				}
				else if ( type === 'columns' || type === 'rows' ) {
					// this has same length as context - one entry for each table
					ret = fn.call( apiInst, context[i], this[i], i );
	
					if ( ret !== undefined ) {
						a.push( ret );
					}
				}
				else if ( type === 'column' || type === 'column-rows' || type === 'row' || type === 'cell' ) {
					// columns and rows share the same structure.
					// 'this' is an array of column indexes for each context
					items = this[i];
	
					if ( type === 'column-rows' ) {
						rows = _selector_row_indexes( context[i], selector.opts );
					}
	
					for ( j=0, jen=items.length ; j<jen ; j++ ) {
						item = items[j];
	
						if ( type === 'cell' ) {
							ret = fn.call( apiInst, context[i], item.row, item.column, i, j );
						}
						else {
							ret = fn.call( apiInst, context[i], item, i, j, rows );
						}
	
						if ( ret !== undefined ) {
							a.push( ret );
						}
					}
				}
			}
	
			if ( a.length || alwaysNew ) {
				var api = new _Api( context, flatten ? a.concat.apply( [], a ) : a );
				var apiSelector = api.selector;
				apiSelector.rows = selector.rows;
				apiSelector.cols = selector.cols;
				apiSelector.opts = selector.opts;
				return api;
			}
			return this;
		},
	
	
		lastIndexOf: __arrayProto.lastIndexOf || function (obj, start)
		{
			// Bit cheeky...
			return this.indexOf.apply( this.toArray.reverse(), arguments );
		},
	
	
		length:  0,
	
	
		map: function ( fn )
		{
			var a = [];
	
			if ( __arrayProto.map ) {
				a = __arrayProto.map.call( this, fn, this );
			}
			else {
				// Compatibility for browsers without EMCA-252-5 (JS 1.6)
				for ( var i=0, ien=this.length ; i<ien ; i++ ) {
					a.push( fn.call( this, this[i], i ) );
				}
			}
	
			return new _Api( this.context, a );
		},
	
	
		pluck: function ( prop )
		{
			return this.map( function ( el ) {
				return el[ prop ];
			} );
		},
	
		pop:     __arrayProto.pop,
	
	
		push:    __arrayProto.push,
	
	
		// Does not return an API instance
		reduce: __arrayProto.reduce || function ( fn, init )
		{
			return _fnReduce( this, fn, init, 0, this.length, 1 );
		},
	
	
		reduceRight: __arrayProto.reduceRight || function ( fn, init )
		{
			return _fnReduce( this, fn, init, this.length-1, -1, -1 );
		},
	
	
		reverse: __arrayProto.reverse,
	
	
		// Object with rows, columns and opts
		selector: null,
	
	
		shift:   __arrayProto.shift,
	
	
		sort:    __arrayProto.sort, // ? name - order?
	
	
		splice:  __arrayProto.splice,
	
	
		toArray: function ()
		{
			return __arrayProto.slice.call( this );
		},
	
	
		to$: function ()
		{
			return $( this );
		},
	
	
		toJQuery: function ()
		{
			return $( this );
		},
	
	
		unique: function ()
		{
			return new _Api( this.context, _unique(this) );
		},
	
	
		unshift: __arrayProto.unshift
	} );
	
	
	_Api.extend = function ( scope, obj, ext )
	{
		// Only extend API instances and static properties of the API
		if ( ! ext.length || ! obj || ( ! (obj instanceof _Api) && ! obj.__dt_wrapper ) ) {
			return;
		}
	
		var
			i, ien,
			j, jen,
			struct, inner,
			methodScoping = function ( scope, fn, struc ) {
				return function () {
					var ret = fn.apply( scope, arguments );
	
					// Method extension
					_Api.extend( ret, ret, struc.methodExt );
					return ret;
				};
			};
	
		for ( i=0, ien=ext.length ; i<ien ; i++ ) {
			struct = ext[i];
	
			// Value
			obj[ struct.name ] = typeof struct.val === 'function' ?
				methodScoping( scope, struct.val, struct ) :
				$.isPlainObject( struct.val ) ?
					{} :
					struct.val;
	
			obj[ struct.name ].__dt_wrapper = true;
	
			// Property extension
			_Api.extend( scope, obj[ struct.name ], struct.propExt );
		}
	};
	
	
	// @todo - Is there need for an augment function?
	// _Api.augment = function ( inst, name )
	// {
	// 	// Find src object in the structure from the name
	// 	var parts = name.split('.');
	
	// 	_Api.extend( inst, obj );
	// };
	
	
	//     [
	//       {
	//         name:      'data'                -- string   - Property name
	//         val:       function () {},       -- function - Api method (or undefined if just an object
	//         methodExt: [ ... ],              -- array    - Array of Api object definitions to extend the method result
	//         propExt:   [ ... ]               -- array    - Array of Api object definitions to extend the property
	//       },
	//       {
	//         name:     'row'
	//         val:       {},
	//         methodExt: [ ... ],
	//         propExt:   [
	//           {
	//             name:      'data'
	//             val:       function () {},
	//             methodExt: [ ... ],
	//             propExt:   [ ... ]
	//           },
	//           ...
	//         ]
	//       }
	//     ]
	
	_Api.register = _api_register = function ( name, val )
	{
		if ( $.isArray( name ) ) {
			for ( var j=0, jen=name.length ; j<jen ; j++ ) {
				_Api.register( name[j], val );
			}
			return;
		}
	
		var
			i, ien,
			heir = name.split('.'),
			struct = __apiStruct,
			key, method;
	
		var find = function ( src, name ) {
			for ( var i=0, ien=src.length ; i<ien ; i++ ) {
				if ( src[i].name === name ) {
					return src[i];
				}
			}
			return null;
		};
	
		for ( i=0, ien=heir.length ; i<ien ; i++ ) {
			method = heir[i].indexOf('()') !== -1;
			key = method ?
				heir[i].replace('()', '') :
				heir[i];
	
			var src = find( struct, key );
			if ( ! src ) {
				src = {
					name:      key,
					val:       {},
					methodExt: [],
					propExt:   []
				};
				struct.push( src );
			}
	
			if ( i === ien-1 ) {
				src.val = val;
			}
			else {
				struct = method ?
					src.methodExt :
					src.propExt;
			}
		}
	};
	
	
	_Api.registerPlural = _api_registerPlural = function ( pluralName, singularName, val ) {
		_Api.register( pluralName, val );
	
		_Api.register( singularName, function () {
			var ret = val.apply( this, arguments );
	
			if ( ret === this ) {
				// Returned item is the API instance that was passed in, return it
				return this;
			}
			else if ( ret instanceof _Api ) {
				// New API instance returned, want the value from the first item
				// in the returned array for the singular result.
				return ret.length ?
					$.isArray( ret[0] ) ?
						new _Api( ret.context, ret[0] ) : // Array results are 'enhanced'
						ret[0] :
					undefined;
			}
	
			// Non-API return - just fire it back
			return ret;
		} );
	};
	
	
	/**
	 * Selector for HTML tables. Apply the given selector to the give array of
	 * DataTables settings objects.
	 *
	 * @param {string|integer} [selector] jQuery selector string or integer
	 * @param  {array} Array of DataTables settings objects to be filtered
	 * @return {array}
	 * @ignore
	 */
	var __table_selector = function ( selector, a )
	{
		// Integer is used to pick out a table by index
		if ( typeof selector === 'number' ) {
			return [ a[ selector ] ];
		}
	
		// Perform a jQuery selector on the table nodes
		var nodes = $.map( a, function (el, i) {
			return el.nTable;
		} );
	
		return $(nodes)
			.filter( selector )
			.map( function (i) {
				// Need to translate back from the table node to the settings
				var idx = $.inArray( this, nodes );
				return a[ idx ];
			} )
			.toArray();
	};
	
	
	
	/**
	 * Context selector for the API's context (i.e. the tables the API instance
	 * refers to.
	 *
	 * @name    DataTable.Api#tables
	 * @param {string|integer} [selector] Selector to pick which tables the iterator
	 *   should operate on. If not given, all tables in the current context are
	 *   used. This can be given as a jQuery selector (for example `':gt(0)'`) to
	 *   select multiple tables or as an integer to select a single table.
	 * @returns {DataTable.Api} Returns a new API instance if a selector is given.
	 */
	_api_register( 'tables()', function ( selector ) {
		// A new instance is created if there was a selector specified
		return selector ?
			new _Api( __table_selector( selector, this.context ) ) :
			this;
	} );
	
	
	_api_register( 'table()', function ( selector ) {
		var tables = this.tables( selector );
		var ctx = tables.context;
	
		// Truncate to the first matched table
		return ctx.length ?
			new _Api( ctx[0] ) :
			tables;
	} );
	
	
	_api_registerPlural( 'tables().nodes()', 'table().node()' , function () {
		return this.iterator( 'table', function ( ctx ) {
			return ctx.nTable;
		}, 1 );
	} );
	
	
	_api_registerPlural( 'tables().body()', 'table().body()' , function () {
		return this.iterator( 'table', function ( ctx ) {
			return ctx.nTBody;
		}, 1 );
	} );
	
	
	_api_registerPlural( 'tables().header()', 'table().header()' , function () {
		return this.iterator( 'table', function ( ctx ) {
			return ctx.nTHead;
		}, 1 );
	} );
	
	
	_api_registerPlural( 'tables().footer()', 'table().footer()' , function () {
		return this.iterator( 'table', function ( ctx ) {
			return ctx.nTFoot;
		}, 1 );
	} );
	
	
	_api_registerPlural( 'tables().containers()', 'table().container()' , function () {
		return this.iterator( 'table', function ( ctx ) {
			return ctx.nTableWrapper;
		}, 1 );
	} );
	
	
	
	/**
	 * Redraw the tables in the current context.
	 */
	_api_register( 'draw()', function ( paging ) {
		return this.iterator( 'table', function ( settings ) {
			if ( paging === 'page' ) {
				_fnDraw( settings );
			}
			else {
				if ( typeof paging === 'string' ) {
					paging = paging === 'full-hold' ?
						false :
						true;
				}
	
				_fnReDraw( settings, paging===false );
			}
		} );
	} );
	
	
	
	/**
	 * Get the current page index.
	 *
	 * @return {integer} Current page index (zero based)
	 *//**
	 * Set the current page.
	 *
	 * Note that if you attempt to show a page which does not exist, DataTables will
	 * not throw an error, but rather reset the paging.
	 *
	 * @param {integer|string} action The paging action to take. This can be one of:
	 *  * `integer` - The page index to jump to
	 *  * `string` - An action to take:
	 *    * `first` - Jump to first page.
	 *    * `next` - Jump to the next page
	 *    * `previous` - Jump to previous page
	 *    * `last` - Jump to the last page.
	 * @returns {DataTables.Api} this
	 */
	_api_register( 'page()', function ( action ) {
		if ( action === undefined ) {
			return this.page.info().page; // not an expensive call
		}
	
		// else, have an action to take on all tables
		return this.iterator( 'table', function ( settings ) {
			_fnPageChange( settings, action );
		} );
	} );
	
	
	/**
	 * Paging information for the first table in the current context.
	 *
	 * If you require paging information for another table, use the `table()` method
	 * with a suitable selector.
	 *
	 * @return {object} Object with the following properties set:
	 *  * `page` - Current page index (zero based - i.e. the first page is `0`)
	 *  * `pages` - Total number of pages
	 *  * `start` - Display index for the first record shown on the current page
	 *  * `end` - Display index for the last record shown on the current page
	 *  * `length` - Display length (number of records). Note that generally `start
	 *    + length = end`, but this is not always true, for example if there are
	 *    only 2 records to show on the final page, with a length of 10.
	 *  * `recordsTotal` - Full data set length
	 *  * `recordsDisplay` - Data set length once the current filtering criterion
	 *    are applied.
	 */
	_api_register( 'page.info()', function ( action ) {
		if ( this.context.length === 0 ) {
			return undefined;
		}
	
		var
			settings   = this.context[0],
			start      = settings._iDisplayStart,
			len        = settings.oFeatures.bPaginate ? settings._iDisplayLength : -1,
			visRecords = settings.fnRecordsDisplay(),
			all        = len === -1;
	
		return {
			"page":           all ? 0 : Math.floor( start / len ),
			"pages":          all ? 1 : Math.ceil( visRecords / len ),
			"start":          start,
			"end":            settings.fnDisplayEnd(),
			"length":         len,
			"recordsTotal":   settings.fnRecordsTotal(),
			"recordsDisplay": visRecords,
			"serverSide":     _fnDataSource( settings ) === 'ssp'
		};
	} );
	
	
	/**
	 * Get the current page length.
	 *
	 * @return {integer} Current page length. Note `-1` indicates that all records
	 *   are to be shown.
	 *//**
	 * Set the current page length.
	 *
	 * @param {integer} Page length to set. Use `-1` to show all records.
	 * @returns {DataTables.Api} this
	 */
	_api_register( 'page.len()', function ( len ) {
		// Note that we can't call this function 'length()' because `length`
		// is a Javascript property of functions which defines how many arguments
		// the function expects.
		if ( len === undefined ) {
			return this.context.length !== 0 ?
				this.context[0]._iDisplayLength :
				undefined;
		}
	
		// else, set the page length
		return this.iterator( 'table', function ( settings ) {
			_fnLengthChange( settings, len );
		} );
	} );
	
	
	
	var __reload = function ( settings, holdPosition, callback ) {
		// Use the draw event to trigger a callback
		if ( callback ) {
			var api = new _Api( settings );
	
			api.one( 'draw', function () {
				callback( api.ajax.json() );
			} );
		}
	
		if ( _fnDataSource( settings ) == 'ssp' ) {
			_fnReDraw( settings, holdPosition );
		}
		else {
			_fnProcessingDisplay( settings, true );
	
			// Cancel an existing request
			var xhr = settings.jqXHR;
			if ( xhr && xhr.readyState !== 4 ) {
				xhr.abort();
			}
	
			// Trigger xhr
			_fnBuildAjax( settings, [], function( json ) {
				_fnClearTable( settings );
	
				var data = _fnAjaxDataSrc( settings, json );
				for ( var i=0, ien=data.length ; i<ien ; i++ ) {
					_fnAddData( settings, data[i] );
				}
	
				_fnReDraw( settings, holdPosition );
				_fnProcessingDisplay( settings, false );
			} );
		}
	};
	
	
	/**
	 * Get the JSON response from the last Ajax request that DataTables made to the
	 * server. Note that this returns the JSON from the first table in the current
	 * context.
	 *
	 * @return {object} JSON received from the server.
	 */
	_api_register( 'ajax.json()', function () {
		var ctx = this.context;
	
		if ( ctx.length > 0 ) {
			return ctx[0].json;
		}
	
		// else return undefined;
	} );
	
	
	/**
	 * Get the data submitted in the last Ajax request
	 */
	_api_register( 'ajax.params()', function () {
		var ctx = this.context;
	
		if ( ctx.length > 0 ) {
			return ctx[0].oAjaxData;
		}
	
		// else return undefined;
	} );
	
	
	/**
	 * Reload tables from the Ajax data source. Note that this function will
	 * automatically re-draw the table when the remote data has been loaded.
	 *
	 * @param {boolean} [reset=true] Reset (default) or hold the current paging
	 *   position. A full re-sort and re-filter is performed when this method is
	 *   called, which is why the pagination reset is the default action.
	 * @returns {DataTables.Api} this
	 */
	_api_register( 'ajax.reload()', function ( callback, resetPaging ) {
		return this.iterator( 'table', function (settings) {
			__reload( settings, resetPaging===false, callback );
		} );
	} );
	
	
	/**
	 * Get the current Ajax URL. Note that this returns the URL from the first
	 * table in the current context.
	 *
	 * @return {string} Current Ajax source URL
	 *//**
	 * Set the Ajax URL. Note that this will set the URL for all tables in the
	 * current context.
	 *
	 * @param {string} url URL to set.
	 * @returns {DataTables.Api} this
	 */
	_api_register( 'ajax.url()', function ( url ) {
		var ctx = this.context;
	
		if ( url === undefined ) {
			// get
			if ( ctx.length === 0 ) {
				return undefined;
			}
			ctx = ctx[0];
	
			return ctx.ajax ?
				$.isPlainObject( ctx.ajax ) ?
					ctx.ajax.url :
					ctx.ajax :
				ctx.sAjaxSource;
		}
	
		// set
		return this.iterator( 'table', function ( settings ) {
			if ( $.isPlainObject( settings.ajax ) ) {
				settings.ajax.url = url;
			}
			else {
				settings.ajax = url;
			}
			// No need to consider sAjaxSource here since DataTables gives priority
			// to `ajax` over `sAjaxSource`. So setting `ajax` here, renders any
			// value of `sAjaxSource` redundant.
		} );
	} );
	
	
	/**
	 * Load data from the newly set Ajax URL. Note that this method is only
	 * available when `ajax.url()` is used to set a URL. Additionally, this method
	 * has the same effect as calling `ajax.reload()` but is provided for
	 * convenience when setting a new URL. Like `ajax.reload()` it will
	 * automatically redraw the table once the remote data has been loaded.
	 *
	 * @returns {DataTables.Api} this
	 */
	_api_register( 'ajax.url().load()', function ( callback, resetPaging ) {
		// Same as a reload, but makes sense to present it for easy access after a
		// url change
		return this.iterator( 'table', function ( ctx ) {
			__reload( ctx, resetPaging===false, callback );
		} );
	} );
	
	
	
	
	var _selector_run = function ( type, selector, selectFn, settings, opts )
	{
		var
			out = [], res,
			a, i, ien, j, jen,
			selectorType = typeof selector;
	
		// Can't just check for isArray here, as an API or jQuery instance might be
		// given with their array like look
		if ( ! selector || selectorType === 'string' || selectorType === 'function' || selector.length === undefined ) {
			selector = [ selector ];
		}
	
		for ( i=0, ien=selector.length ; i<ien ; i++ ) {
			a = selector[i] && selector[i].split ?
				selector[i].split(',') :
				[ selector[i] ];
	
			for ( j=0, jen=a.length ; j<jen ; j++ ) {
				res = selectFn( typeof a[j] === 'string' ? $.trim(a[j]) : a[j] );
	
				if ( res && res.length ) {
					out = out.concat( res );
				}
			}
		}
	
		// selector extensions
		var ext = _ext.selector[ type ];
		if ( ext.length ) {
			for ( i=0, ien=ext.length ; i<ien ; i++ ) {
				out = ext[i]( settings, opts, out );
			}
		}
	
		return _unique( out );
	};
	
	
	var _selector_opts = function ( opts )
	{
		if ( ! opts ) {
			opts = {};
		}
	
		// Backwards compatibility for 1.9- which used the terminology filter rather
		// than search
		if ( opts.filter && opts.search === undefined ) {
			opts.search = opts.filter;
		}
	
		return $.extend( {
			search: 'none',
			order: 'current',
			page: 'all'
		}, opts );
	};
	
	
	var _selector_first = function ( inst )
	{
		// Reduce the API instance to the first item found
		for ( var i=0, ien=inst.length ; i<ien ; i++ ) {
			if ( inst[i].length > 0 ) {
				// Assign the first element to the first item in the instance
				// and truncate the instance and context
				inst[0] = inst[i];
				inst[0].length = 1;
				inst.length = 1;
				inst.context = [ inst.context[i] ];
	
				return inst;
			}
		}
	
		// Not found - return an empty instance
		inst.length = 0;
		return inst;
	};
	
	
	var _selector_row_indexes = function ( settings, opts )
	{
		var
			i, ien, tmp, a=[],
			displayFiltered = settings.aiDisplay,
			displayMaster = settings.aiDisplayMaster;
	
		var
			search = opts.search,  // none, applied, removed
			order  = opts.order,   // applied, current, index (original - compatibility with 1.9)
			page   = opts.page;    // all, current
	
		if ( _fnDataSource( settings ) == 'ssp' ) {
			// In server-side processing mode, most options are irrelevant since
			// rows not shown don't exist and the index order is the applied order
			// Removed is a special case - for consistency just return an empty
			// array
			return search === 'removed' ?
				[] :
				_range( 0, displayMaster.length );
		}
		else if ( page == 'current' ) {
			// Current page implies that order=current and fitler=applied, since it is
			// fairly senseless otherwise, regardless of what order and search actually
			// are
			for ( i=settings._iDisplayStart, ien=settings.fnDisplayEnd() ; i<ien ; i++ ) {
				a.push( displayFiltered[i] );
			}
		}
		else if ( order == 'current' || order == 'applied' ) {
			a = search == 'none' ?
				displayMaster.slice() :                      // no search
				search == 'applied' ?
					displayFiltered.slice() :                // applied search
					$.map( displayMaster, function (el, i) { // removed search
						return $.inArray( el, displayFiltered ) === -1 ? el : null;
					} );
		}
		else if ( order == 'index' || order == 'original' ) {
			for ( i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
				if ( search == 'none' ) {
					a.push( i );
				}
				else { // applied | removed
					tmp = $.inArray( i, displayFiltered );
	
					if ((tmp === -1 && search == 'removed') ||
						(tmp >= 0   && search == 'applied') )
					{
						a.push( i );
					}
				}
			}
		}
	
		return a;
	};
	
	
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Rows
	 *
	 * {}          - no selector - use all available rows
	 * {integer}   - row aoData index
	 * {node}      - TR node
	 * {string}    - jQuery selector to apply to the TR elements
	 * {array}     - jQuery array of nodes, or simply an array of TR nodes
	 *
	 */
	
	
	var __row_selector = function ( settings, selector, opts )
	{
		var run = function ( sel ) {
			var selInt = _intVal( sel );
			var i, ien;
	
			// Short cut - selector is a number and no options provided (default is
			// all records, so no need to check if the index is in there, since it
			// must be - dev error if the index doesn't exist).
			if ( selInt !== null && ! opts ) {
				return [ selInt ];
			}
	
			var rows = _selector_row_indexes( settings, opts );
	
			if ( selInt !== null && $.inArray( selInt, rows ) !== -1 ) {
				// Selector - integer
				return [ selInt ];
			}
			else if ( ! sel ) {
				// Selector - none
				return rows;
			}
	
			// Selector - function
			if ( typeof sel === 'function' ) {
				return $.map( rows, function (idx) {
					var row = settings.aoData[ idx ];
					return sel( idx, row._aData, row.nTr ) ? idx : null;
				} );
			}
	
			// Get nodes in the order from the `rows` array with null values removed
			var nodes = _removeEmpty(
				_pluck_order( settings.aoData, rows, 'nTr' )
			);
	
			// Selector - node
			if ( sel.nodeName ) {
				if ( sel._DT_RowIndex !== undefined ) {
					return [ sel._DT_RowIndex ]; // Property added by DT for fast lookup
				}
				else if ( sel._DT_CellIndex ) {
					return [ sel._DT_CellIndex.row ];
				}
				else {
					var host = $(sel).closest('*[data-dt-row]');
					return host.length ?
						[ host.data('dt-row') ] :
						[];
				}
			}
	
			// ID selector. Want to always be able to select rows by id, regardless
			// of if the tr element has been created or not, so can't rely upon
			// jQuery here - hence a custom implementation. This does not match
			// Sizzle's fast selector or HTML4 - in HTML5 the ID can be anything,
			// but to select it using a CSS selector engine (like Sizzle or
			// querySelect) it would need to need to be escaped for some characters.
			// DataTables simplifies this for row selectors since you can select
			// only a row. A # indicates an id any anything that follows is the id -
			// unescaped.
			if ( typeof sel === 'string' && sel.charAt(0) === '#' ) {
				// get row index from id
				var rowObj = settings.aIds[ sel.replace( /^#/, '' ) ];
				if ( rowObj !== undefined ) {
					return [ rowObj.idx ];
				}
	
				// need to fall through to jQuery in case there is DOM id that
				// matches
			}
	
			// Selector - jQuery selector string, array of nodes or jQuery object/
			// As jQuery's .filter() allows jQuery objects to be passed in filter,
			// it also allows arrays, so this will cope with all three options
			return $(nodes)
				.filter( sel )
				.map( function () {
					return this._DT_RowIndex;
				} )
				.toArray();
		};
	
		return _selector_run( 'row', selector, run, settings, opts );
	};
	
	
	_api_register( 'rows()', function ( selector, opts ) {
		// argument shifting
		if ( selector === undefined ) {
			selector = '';
		}
		else if ( $.isPlainObject( selector ) ) {
			opts = selector;
			selector = '';
		}
	
		opts = _selector_opts( opts );
	
		var inst = this.iterator( 'table', function ( settings ) {
			return __row_selector( settings, selector, opts );
		}, 1 );
	
		// Want argument shifting here and in __row_selector?
		inst.selector.rows = selector;
		inst.selector.opts = opts;
	
		return inst;
	} );
	
	_api_register( 'rows().nodes()', function () {
		return this.iterator( 'row', function ( settings, row ) {
			return settings.aoData[ row ].nTr || undefined;
		}, 1 );
	} );
	
	_api_register( 'rows().data()', function () {
		return this.iterator( true, 'rows', function ( settings, rows ) {
			return _pluck_order( settings.aoData, rows, '_aData' );
		}, 1 );
	} );
	
	_api_registerPlural( 'rows().cache()', 'row().cache()', function ( type ) {
		return this.iterator( 'row', function ( settings, row ) {
			var r = settings.aoData[ row ];
			return type === 'search' ? r._aFilterData : r._aSortData;
		}, 1 );
	} );
	
	_api_registerPlural( 'rows().invalidate()', 'row().invalidate()', function ( src ) {
		return this.iterator( 'row', function ( settings, row ) {
			_fnInvalidate( settings, row, src );
		} );
	} );
	
	_api_registerPlural( 'rows().indexes()', 'row().index()', function () {
		return this.iterator( 'row', function ( settings, row ) {
			return row;
		}, 1 );
	} );
	
	_api_registerPlural( 'rows().ids()', 'row().id()', function ( hash ) {
		var a = [];
		var context = this.context;
	
		// `iterator` will drop undefined values, but in this case we want them
		for ( var i=0, ien=context.length ; i<ien ; i++ ) {
			for ( var j=0, jen=this[i].length ; j<jen ; j++ ) {
				var id = context[i].rowIdFn( context[i].aoData[ this[i][j] ]._aData );
				a.push( (hash === true ? '#' : '' )+ id );
			}
		}
	
		return new _Api( context, a );
	} );
	
	_api_registerPlural( 'rows().remove()', 'row().remove()', function () {
		var that = this;
	
		this.iterator( 'row', function ( settings, row, thatIdx ) {
			var data = settings.aoData;
			var rowData = data[ row ];
			var i, ien, j, jen;
			var loopRow, loopCells;
	
			data.splice( row, 1 );
	
			// Update the cached indexes
			for ( i=0, ien=data.length ; i<ien ; i++ ) {
				loopRow = data[i];
				loopCells = loopRow.anCells;
	
				// Rows
				if ( loopRow.nTr !== null ) {
					loopRow.nTr._DT_RowIndex = i;
				}
	
				// Cells
				if ( loopCells !== null ) {
					for ( j=0, jen=loopCells.length ; j<jen ; j++ ) {
						loopCells[j]._DT_CellIndex.row = i;
					}
				}
			}
	
			// Delete from the display arrays
			_fnDeleteIndex( settings.aiDisplayMaster, row );
			_fnDeleteIndex( settings.aiDisplay, row );
			_fnDeleteIndex( that[ thatIdx ], row, false ); // maintain local indexes
	
			// Check for an 'overflow' they case for displaying the table
			_fnLengthOverflow( settings );
	
			// Remove the row's ID reference if there is one
			var id = settings.rowIdFn( rowData._aData );
			if ( id !== undefined ) {
				delete settings.aIds[ id ];
			}
		} );
	
		this.iterator( 'table', function ( settings ) {
			for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
				settings.aoData[i].idx = i;
			}
		} );
	
		return this;
	} );
	
	
	_api_register( 'rows.add()', function ( rows ) {
		var newRows = this.iterator( 'table', function ( settings ) {
				var row, i, ien;
				var out = [];
	
				for ( i=0, ien=rows.length ; i<ien ; i++ ) {
					row = rows[i];
	
					if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) {
						out.push( _fnAddTr( settings, row )[0] );
					}
					else {
						out.push( _fnAddData( settings, row ) );
					}
				}
	
				return out;
			}, 1 );
	
		// Return an Api.rows() extended instance, so rows().nodes() etc can be used
		var modRows = this.rows( -1 );
		modRows.pop();
		$.merge( modRows, newRows );
	
		return modRows;
	} );
	
	
	
	
	
	/**
	 *
	 */
	_api_register( 'row()', function ( selector, opts ) {
		return _selector_first( this.rows( selector, opts ) );
	} );
	
	
	_api_register( 'row().data()', function ( data ) {
		var ctx = this.context;
	
		if ( data === undefined ) {
			// Get
			return ctx.length && this.length ?
				ctx[0].aoData[ this[0] ]._aData :
				undefined;
		}
	
		// Set
		ctx[0].aoData[ this[0] ]._aData = data;
	
		// Automatically invalidate
		_fnInvalidate( ctx[0], this[0], 'data' );
	
		return this;
	} );
	
	
	_api_register( 'row().node()', function () {
		var ctx = this.context;
	
		return ctx.length && this.length ?
			ctx[0].aoData[ this[0] ].nTr || null :
			null;
	} );
	
	
	_api_register( 'row.add()', function ( row ) {
		// Allow a jQuery object to be passed in - only a single row is added from
		// it though - the first element in the set
		if ( row instanceof $ && row.length ) {
			row = row[0];
		}
	
		var rows = this.iterator( 'table', function ( settings ) {
			if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) {
				return _fnAddTr( settings, row )[0];
			}
			return _fnAddData( settings, row );
		} );
	
		// Return an Api.rows() extended instance, with the newly added row selected
		return this.row( rows[0] );
	} );
	
	
	
	var __details_add = function ( ctx, row, data, klass )
	{
		// Convert to array of TR elements
		var rows = [];
		var addRow = function ( r, k ) {
			// Recursion to allow for arrays of jQuery objects
			if ( $.isArray( r ) || r instanceof $ ) {
				for ( var i=0, ien=r.length ; i<ien ; i++ ) {
					addRow( r[i], k );
				}
				return;
			}
	
			// If we get a TR element, then just add it directly - up to the dev
			// to add the correct number of columns etc
			if ( r.nodeName && r.nodeName.toLowerCase() === 'tr' ) {
				rows.push( r );
			}
			else {
				// Otherwise create a row with a wrapper
				var created = $('<tr><td/></tr>').addClass( k );
				$('td', created)
					.addClass( k )
					.html( r )
					[0].colSpan = _fnVisbleColumns( ctx );
	
				rows.push( created[0] );
			}
		};
	
		addRow( data, klass );
	
		if ( row._details ) {
			row._details.remove();
		}
	
		row._details = $(rows);
	
		// If the children were already shown, that state should be retained
		if ( row._detailsShow ) {
			row._details.insertAfter( row.nTr );
		}
	};
	
	
	var __details_remove = function ( api, idx )
	{
		var ctx = api.context;
	
		if ( ctx.length ) {
			var row = ctx[0].aoData[ idx !== undefined ? idx : api[0] ];
	
			if ( row && row._details ) {
				row._details.remove();
	
				row._detailsShow = undefined;
				row._details = undefined;
			}
		}
	};
	
	
	var __details_display = function ( api, show ) {
		var ctx = api.context;
	
		if ( ctx.length && api.length ) {
			var row = ctx[0].aoData[ api[0] ];
	
			if ( row._details ) {
				row._detailsShow = show;
	
				if ( show ) {
					row._details.insertAfter( row.nTr );
				}
				else {
					row._details.detach();
				}
	
				__details_events( ctx[0] );
			}
		}
	};
	
	
	var __details_events = function ( settings )
	{
		var api = new _Api( settings );
		var namespace = '.dt.DT_details';
		var drawEvent = 'draw'+namespace;
		var colvisEvent = 'column-visibility'+namespace;
		var destroyEvent = 'destroy'+namespace;
		var data = settings.aoData;
	
		api.off( drawEvent +' '+ colvisEvent +' '+ destroyEvent );
	
		if ( _pluck( data, '_details' ).length > 0 ) {
			// On each draw, insert the required elements into the document
			api.on( drawEvent, function ( e, ctx ) {
				if ( settings !== ctx ) {
					return;
				}
	
				api.rows( {page:'current'} ).eq(0).each( function (idx) {
					// Internal data grab
					var row = data[ idx ];
	
					if ( row._detailsShow ) {
						row._details.insertAfter( row.nTr );
					}
				} );
			} );
	
			// Column visibility change - update the colspan
			api.on( colvisEvent, function ( e, ctx, idx, vis ) {
				if ( settings !== ctx ) {
					return;
				}
	
				// Update the colspan for the details rows (note, only if it already has
				// a colspan)
				var row, visible = _fnVisbleColumns( ctx );
	
				for ( var i=0, ien=data.length ; i<ien ; i++ ) {
					row = data[i];
	
					if ( row._details ) {
						row._details.children('td[colspan]').attr('colspan', visible );
					}
				}
			} );
	
			// Table destroyed - nuke any child rows
			api.on( destroyEvent, function ( e, ctx ) {
				if ( settings !== ctx ) {
					return;
				}
	
				for ( var i=0, ien=data.length ; i<ien ; i++ ) {
					if ( data[i]._details ) {
						__details_remove( api, i );
					}
				}
			} );
		}
	};
	
	// Strings for the method names to help minification
	var _emp = '';
	var _child_obj = _emp+'row().child';
	var _child_mth = _child_obj+'()';
	
	// data can be:
	//  tr
	//  string
	//  jQuery or array of any of the above
	_api_register( _child_mth, function ( data, klass ) {
		var ctx = this.context;
	
		if ( data === undefined ) {
			// get
			return ctx.length && this.length ?
				ctx[0].aoData[ this[0] ]._details :
				undefined;
		}
		else if ( data === true ) {
			// show
			this.child.show();
		}
		else if ( data === false ) {
			// remove
			__details_remove( this );
		}
		else if ( ctx.length && this.length ) {
			// set
			__details_add( ctx[0], ctx[0].aoData[ this[0] ], data, klass );
		}
	
		return this;
	} );
	
	
	_api_register( [
		_child_obj+'.show()',
		_child_mth+'.show()' // only when `child()` was called with parameters (without
	], function ( show ) {   // it returns an object and this method is not executed)
		__details_display( this, true );
		return this;
	} );
	
	
	_api_register( [
		_child_obj+'.hide()',
		_child_mth+'.hide()' // only when `child()` was called with parameters (without
	], function () {         // it returns an object and this method is not executed)
		__details_display( this, false );
		return this;
	} );
	
	
	_api_register( [
		_child_obj+'.remove()',
		_child_mth+'.remove()' // only when `child()` was called with parameters (without
	], function () {           // it returns an object and this method is not executed)
		__details_remove( this );
		return this;
	} );
	
	
	_api_register( _child_obj+'.isShown()', function () {
		var ctx = this.context;
	
		if ( ctx.length && this.length ) {
			// _detailsShown as false or undefined will fall through to return false
			return ctx[0].aoData[ this[0] ]._detailsShow || false;
		}
		return false;
	} );
	
	
	
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Columns
	 *
	 * {integer}           - column index (>=0 count from left, <0 count from right)
	 * "{integer}:visIdx"  - visible column index (i.e. translate to column index)  (>=0 count from left, <0 count from right)
	 * "{integer}:visible" - alias for {integer}:visIdx  (>=0 count from left, <0 count from right)
	 * "{string}:name"     - column name
	 * "{string}"          - jQuery selector on column header nodes
	 *
	 */
	
	// can be an array of these items, comma separated list, or an array of comma
	// separated lists
	
	var __re_column_selector = /^(.+):(name|visIdx|visible)$/;
	
	
	// r1 and r2 are redundant - but it means that the parameters match for the
	// iterator callback in columns().data()
	var __columnData = function ( settings, column, r1, r2, rows ) {
		var a = [];
		for ( var row=0, ien=rows.length ; row<ien ; row++ ) {
			a.push( _fnGetCellData( settings, rows[row], column ) );
		}
		return a;
	};
	
	
	var __column_selector = function ( settings, selector, opts )
	{
		var
			columns = settings.aoColumns,
			names = _pluck( columns, 'sName' ),
			nodes = _pluck( columns, 'nTh' );
	
		var run = function ( s ) {
			var selInt = _intVal( s );
	
			// Selector - all
			if ( s === '' ) {
				return _range( columns.length );
			}
	
			// Selector - index
			if ( selInt !== null ) {
				return [ selInt >= 0 ?
					selInt : // Count from left
					columns.length + selInt // Count from right (+ because its a negative value)
				];
			}
	
			// Selector = function
			if ( typeof s === 'function' ) {
				var rows = _selector_row_indexes( settings, opts );
	
				return $.map( columns, function (col, idx) {
					return s(
							idx,
							__columnData( settings, idx, 0, 0, rows ),
							nodes[ idx ]
						) ? idx : null;
				} );
			}
	
			// jQuery or string selector
			var match = typeof s === 'string' ?
				s.match( __re_column_selector ) :
				'';
	
			if ( match ) {
				switch( match[2] ) {
					case 'visIdx':
					case 'visible':
						var idx = parseInt( match[1], 10 );
						// Visible index given, convert to column index
						if ( idx < 0 ) {
							// Counting from the right
							var visColumns = $.map( columns, function (col,i) {
								return col.bVisible ? i : null;
							} );
							return [ visColumns[ visColumns.length + idx ] ];
						}
						// Counting from the left
						return [ _fnVisibleToColumnIndex( settings, idx ) ];
	
					case 'name':
						// match by name. `names` is column index complete and in order
						return $.map( names, function (name, i) {
							return name === match[1] ? i : null;
						} );
	
					default:
						return [];
				}
			}
	
			// Cell in the table body
			if ( s.nodeName && s._DT_CellIndex ) {
				return [ s._DT_CellIndex.column ];
			}
	
			// jQuery selector on the TH elements for the columns
			var jqResult = $( nodes )
				.filter( s )
				.map( function () {
					return $.inArray( this, nodes ); // `nodes` is column index complete and in order
				} )
				.toArray();
	
			if ( jqResult.length || ! s.nodeName ) {
				return jqResult;
			}
	
			// Otherwise a node which might have a `dt-column` data attribute, or be
			// a child or such an element
			var host = $(s).closest('*[data-dt-column]');
			return host.length ?
				[ host.data('dt-column') ] :
				[];
		};
	
		return _selector_run( 'column', selector, run, settings, opts );
	};
	
	
	var __setColumnVis = function ( settings, column, vis ) {
		var
			cols = settings.aoColumns,
			col  = cols[ column ],
			data = settings.aoData,
			row, cells, i, ien, tr;
	
		// Get
		if ( vis === undefined ) {
			return col.bVisible;
		}
	
		// Set
		// No change
		if ( col.bVisible === vis ) {
			return;
		}
	
		if ( vis ) {
			// Insert column
			// Need to decide if we should use appendChild or insertBefore
			var insertBefore = $.inArray( true, _pluck(cols, 'bVisible'), column+1 );
	
			for ( i=0, ien=data.length ; i<ien ; i++ ) {
				tr = data[i].nTr;
				cells = data[i].anCells;
	
				if ( tr ) {
					// insertBefore can act like appendChild if 2nd arg is null
					tr.insertBefore( cells[ column ], cells[ insertBefore ] || null );
				}
			}
		}
		else {
			// Remove column
			$( _pluck( settings.aoData, 'anCells', column ) ).detach();
		}
	
		// Common actions
		col.bVisible = vis;
		_fnDrawHead( settings, settings.aoHeader );
		_fnDrawHead( settings, settings.aoFooter );
	
		_fnSaveState( settings );
	};
	
	
	_api_register( 'columns()', function ( selector, opts ) {
		// argument shifting
		if ( selector === undefined ) {
			selector = '';
		}
		else if ( $.isPlainObject( selector ) ) {
			opts = selector;
			selector = '';
		}
	
		opts = _selector_opts( opts );
	
		var inst = this.iterator( 'table', function ( settings ) {
			return __column_selector( settings, selector, opts );
		}, 1 );
	
		// Want argument shifting here and in _row_selector?
		inst.selector.cols = selector;
		inst.selector.opts = opts;
	
		return inst;
	} );
	
	_api_registerPlural( 'columns().header()', 'column().header()', function ( selector, opts ) {
		return this.iterator( 'column', function ( settings, column ) {
			return settings.aoColumns[column].nTh;
		}, 1 );
	} );
	
	_api_registerPlural( 'columns().footer()', 'column().footer()', function ( selector, opts ) {
		return this.iterator( 'column', function ( settings, column ) {
			return settings.aoColumns[column].nTf;
		}, 1 );
	} );
	
	_api_registerPlural( 'columns().data()', 'column().data()', function () {
		return this.iterator( 'column-rows', __columnData, 1 );
	} );
	
	_api_registerPlural( 'columns().dataSrc()', 'column().dataSrc()', function () {
		return this.iterator( 'column', function ( settings, column ) {
			return settings.aoColumns[column].mData;
		}, 1 );
	} );
	
	_api_registerPlural( 'columns().cache()', 'column().cache()', function ( type ) {
		return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) {
			return _pluck_order( settings.aoData, rows,
				type === 'search' ? '_aFilterData' : '_aSortData', column
			);
		}, 1 );
	} );
	
	_api_registerPlural( 'columns().nodes()', 'column().nodes()', function () {
		return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) {
			return _pluck_order( settings.aoData, rows, 'anCells', column ) ;
		}, 1 );
	} );
	
	_api_registerPlural( 'columns().visible()', 'column().visible()', function ( vis, calc ) {
		var ret = this.iterator( 'column', function ( settings, column ) {
			if ( vis === undefined ) {
				return settings.aoColumns[ column ].bVisible;
			} // else
			__setColumnVis( settings, column, vis );
		} );
	
		// Group the column visibility changes
		if ( vis !== undefined ) {
			// Second loop once the first is done for events
			this.iterator( 'column', function ( settings, column ) {
				_fnCallbackFire( settings, null, 'column-visibility', [settings, column, vis, calc] );
			} );
	
			if ( calc === undefined || calc ) {
				this.columns.adjust();
			}
		}
	
		return ret;
	} );
	
	_api_registerPlural( 'columns().indexes()', 'column().index()', function ( type ) {
		return this.iterator( 'column', function ( settings, column ) {
			return type === 'visible' ?
				_fnColumnIndexToVisible( settings, column ) :
				column;
		}, 1 );
	} );
	
	_api_register( 'columns.adjust()', function () {
		return this.iterator( 'table', function ( settings ) {
			_fnAdjustColumnSizing( settings );
		}, 1 );
	} );
	
	_api_register( 'column.index()', function ( type, idx ) {
		if ( this.context.length !== 0 ) {
			var ctx = this.context[0];
	
			if ( type === 'fromVisible' || type === 'toData' ) {
				return _fnVisibleToColumnIndex( ctx, idx );
			}
			else if ( type === 'fromData' || type === 'toVisible' ) {
				return _fnColumnIndexToVisible( ctx, idx );
			}
		}
	} );
	
	_api_register( 'column()', function ( selector, opts ) {
		return _selector_first( this.columns( selector, opts ) );
	} );
	
	
	
	var __cell_selector = function ( settings, selector, opts )
	{
		var data = settings.aoData;
		var rows = _selector_row_indexes( settings, opts );
		var cells = _removeEmpty( _pluck_order( data, rows, 'anCells' ) );
		var allCells = $( [].concat.apply([], cells) );
		var row;
		var columns = settings.aoColumns.length;
		var a, i, ien, j, o, host;
	
		var run = function ( s ) {
			var fnSelector = typeof s === 'function';
	
			if ( s === null || s === undefined || fnSelector ) {
				// All cells and function selectors
				a = [];
	
				for ( i=0, ien=rows.length ; i<ien ; i++ ) {
					row = rows[i];
	
					for ( j=0 ; j<columns ; j++ ) {
						o = {
							row: row,
							column: j
						};
	
						if ( fnSelector ) {
							// Selector - function
							host = data[ row ];
	
							if ( s( o, _fnGetCellData(settings, row, j), host.anCells ? host.anCells[j] : null ) ) {
								a.push( o );
							}
						}
						else {
							// Selector - all
							a.push( o );
						}
					}
				}
	
				return a;
			}
			
			// Selector - index
			if ( $.isPlainObject( s ) ) {
				return [s];
			}
	
			// Selector - jQuery filtered cells
			var jqResult = allCells
				.filter( s )
				.map( function (i, el) {
					return { // use a new object, in case someone changes the values
						row:    el._DT_CellIndex.row,
						column: el._DT_CellIndex.column
	 				};
				} )
				.toArray();
	
			if ( jqResult.length || ! s.nodeName ) {
				return jqResult;
			}
	
			// Otherwise the selector is a node, and there is one last option - the
			// element might be a child of an element which has dt-row and dt-column
			// data attributes
			host = $(s).closest('*[data-dt-row]');
			return host.length ?
				[ {
					row: host.data('dt-row'),
					column: host.data('dt-column')
				} ] :
				[];
		};
	
		return _selector_run( 'cell', selector, run, settings, opts );
	};
	
	
	
	
	_api_register( 'cells()', function ( rowSelector, columnSelector, opts ) {
		// Argument shifting
		if ( $.isPlainObject( rowSelector ) ) {
			// Indexes
			if ( rowSelector.row === undefined ) {
				// Selector options in first parameter
				opts = rowSelector;
				rowSelector = null;
			}
			else {
				// Cell index objects in first parameter
				opts = columnSelector;
				columnSelector = null;
			}
		}
		if ( $.isPlainObject( columnSelector ) ) {
			opts = columnSelector;
			columnSelector = null;
		}
	
		// Cell selector
		if ( columnSelector === null || columnSelector === undefined ) {
			return this.iterator( 'table', function ( settings ) {
				return __cell_selector( settings, rowSelector, _selector_opts( opts ) );
			} );
		}
	
		// Row + column selector
		var columns = this.columns( columnSelector, opts );
		var rows = this.rows( rowSelector, opts );
		var a, i, ien, j, jen;
	
		var cells = this.iterator( 'table', function ( settings, idx ) {
			a = [];
	
			for ( i=0, ien=rows[idx].length ; i<ien ; i++ ) {
				for ( j=0, jen=columns[idx].length ; j<jen ; j++ ) {
					a.push( {
						row:    rows[idx][i],
						column: columns[idx][j]
					} );
				}
			}
	
			return a;
		}, 1 );
	
		$.extend( cells.selector, {
			cols: columnSelector,
			rows: rowSelector,
			opts: opts
		} );
	
		return cells;
	} );
	
	
	_api_registerPlural( 'cells().nodes()', 'cell().node()', function () {
		return this.iterator( 'cell', function ( settings, row, column ) {
			var data = settings.aoData[ row ];
	
			return data && data.anCells ?
				data.anCells[ column ] :
				undefined;
		}, 1 );
	} );
	
	
	_api_register( 'cells().data()', function () {
		return this.iterator( 'cell', function ( settings, row, column ) {
			return _fnGetCellData( settings, row, column );
		}, 1 );
	} );
	
	
	_api_registerPlural( 'cells().cache()', 'cell().cache()', function ( type ) {
		type = type === 'search' ? '_aFilterData' : '_aSortData';
	
		return this.iterator( 'cell', function ( settings, row, column ) {
			return settings.aoData[ row ][ type ][ column ];
		}, 1 );
	} );
	
	
	_api_registerPlural( 'cells().render()', 'cell().render()', function ( type ) {
		return this.iterator( 'cell', function ( settings, row, column ) {
			return _fnGetCellData( settings, row, column, type );
		}, 1 );
	} );
	
	
	_api_registerPlural( 'cells().indexes()', 'cell().index()', function () {
		return this.iterator( 'cell', function ( settings, row, column ) {
			return {
				row: row,
				column: column,
				columnVisible: _fnColumnIndexToVisible( settings, column )
			};
		}, 1 );
	} );
	
	
	_api_registerPlural( 'cells().invalidate()', 'cell().invalidate()', function ( src ) {
		return this.iterator( 'cell', function ( settings, row, column ) {
			_fnInvalidate( settings, row, src, column );
		} );
	} );
	
	
	
	_api_register( 'cell()', function ( rowSelector, columnSelector, opts ) {
		return _selector_first( this.cells( rowSelector, columnSelector, opts ) );
	} );
	
	
	_api_register( 'cell().data()', function ( data ) {
		var ctx = this.context;
		var cell = this[0];
	
		if ( data === undefined ) {
			// Get
			return ctx.length && cell.length ?
				_fnGetCellData( ctx[0], cell[0].row, cell[0].column ) :
				undefined;
		}
	
		// Set
		_fnSetCellData( ctx[0], cell[0].row, cell[0].column, data );
		_fnInvalidate( ctx[0], cell[0].row, 'data', cell[0].column );
	
		return this;
	} );
	
	
	
	/**
	 * Get current ordering (sorting) that has been applied to the table.
	 *
	 * @returns {array} 2D array containing the sorting information for the first
	 *   table in the current context. Each element in the parent array represents
	 *   a column being sorted upon (i.e. multi-sorting with two columns would have
	 *   2 inner arrays). The inner arrays may have 2 or 3 elements. The first is
	 *   the column index that the sorting condition applies to, the second is the
	 *   direction of the sort (`desc` or `asc`) and, optionally, the third is the
	 *   index of the sorting order from the `column.sorting` initialisation array.
	 *//**
	 * Set the ordering for the table.
	 *
	 * @param {integer} order Column index to sort upon.
	 * @param {string} direction Direction of the sort to be applied (`asc` or `desc`)
	 * @returns {DataTables.Api} this
	 *//**
	 * Set the ordering for the table.
	 *
	 * @param {array} order 1D array of sorting information to be applied.
	 * @param {array} [...] Optional additional sorting conditions
	 * @returns {DataTables.Api} this
	 *//**
	 * Set the ordering for the table.
	 *
	 * @param {array} order 2D array of sorting information to be applied.
	 * @returns {DataTables.Api} this
	 */
	_api_register( 'order()', function ( order, dir ) {
		var ctx = this.context;
	
		if ( order === undefined ) {
			// get
			return ctx.length !== 0 ?
				ctx[0].aaSorting :
				undefined;
		}
	
		// set
		if ( typeof order === 'number' ) {
			// Simple column / direction passed in
			order = [ [ order, dir ] ];
		}
		else if ( order.length && ! $.isArray( order[0] ) ) {
			// Arguments passed in (list of 1D arrays)
			order = Array.prototype.slice.call( arguments );
		}
		// otherwise a 2D array was passed in
	
		return this.iterator( 'table', function ( settings ) {
			settings.aaSorting = order.slice();
		} );
	} );
	
	
	/**
	 * Attach a sort listener to an element for a given column
	 *
	 * @param {node|jQuery|string} node Identifier for the element(s) to attach the
	 *   listener to. This can take the form of a single DOM node, a jQuery
	 *   collection of nodes or a jQuery selector which will identify the node(s).
	 * @param {integer} column the column that a click on this node will sort on
	 * @param {function} [callback] callback function when sort is run
	 * @returns {DataTables.Api} this
	 */
	_api_register( 'order.listener()', function ( node, column, callback ) {
		return this.iterator( 'table', function ( settings ) {
			_fnSortAttachListener( settings, node, column, callback );
		} );
	} );
	
	
	_api_register( 'order.fixed()', function ( set ) {
		if ( ! set ) {
			var ctx = this.context;
			var fixed = ctx.length ?
				ctx[0].aaSortingFixed :
				undefined;
	
			return $.isArray( fixed ) ?
				{ pre: fixed } :
				fixed;
		}
	
		return this.iterator( 'table', function ( settings ) {
			settings.aaSortingFixed = $.extend( true, {}, set );
		} );
	} );
	
	
	// Order by the selected column(s)
	_api_register( [
		'columns().order()',
		'column().order()'
	], function ( dir ) {
		var that = this;
	
		return this.iterator( 'table', function ( settings, i ) {
			var sort = [];
	
			$.each( that[i], function (j, col) {
				sort.push( [ col, dir ] );
			} );
	
			settings.aaSorting = sort;
		} );
	} );
	
	
	
	_api_register( 'search()', function ( input, regex, smart, caseInsen ) {
		var ctx = this.context;
	
		if ( input === undefined ) {
			// get
			return ctx.length !== 0 ?
				ctx[0].oPreviousSearch.sSearch :
				undefined;
		}
	
		// set
		return this.iterator( 'table', function ( settings ) {
			if ( ! settings.oFeatures.bFilter ) {
				return;
			}
	
			_fnFilterComplete( settings, $.extend( {}, settings.oPreviousSearch, {
				"sSearch": input+"",
				"bRegex":  regex === null ? false : regex,
				"bSmart":  smart === null ? true  : smart,
				"bCaseInsensitive": caseInsen === null ? true : caseInsen
			} ), 1 );
		} );
	} );
	
	
	_api_registerPlural(
		'columns().search()',
		'column().search()',
		function ( input, regex, smart, caseInsen ) {
			return this.iterator( 'column', function ( settings, column ) {
				var preSearch = settings.aoPreSearchCols;
	
				if ( input === undefined ) {
					// get
					return preSearch[ column ].sSearch;
				}
	
				// set
				if ( ! settings.oFeatures.bFilter ) {
					return;
				}
	
				$.extend( preSearch[ column ], {
					"sSearch": input+"",
					"bRegex":  regex === null ? false : regex,
					"bSmart":  smart === null ? true  : smart,
					"bCaseInsensitive": caseInsen === null ? true : caseInsen
				} );
	
				_fnFilterComplete( settings, settings.oPreviousSearch, 1 );
			} );
		}
	);
	
	/*
	 * State API methods
	 */
	
	_api_register( 'state()', function () {
		return this.context.length ?
			this.context[0].oSavedState :
			null;
	} );
	
	
	_api_register( 'state.clear()', function () {
		return this.iterator( 'table', function ( settings ) {
			// Save an empty object
			settings.fnStateSaveCallback.call( settings.oInstance, settings, {} );
		} );
	} );
	
	
	_api_register( 'state.loaded()', function () {
		return this.context.length ?
			this.context[0].oLoadedState :
			null;
	} );
	
	
	_api_register( 'state.save()', function () {
		return this.iterator( 'table', function ( settings ) {
			_fnSaveState( settings );
		} );
	} );
	
	
	
	/**
	 * Provide a common method for plug-ins to check the version of DataTables being
	 * used, in order to ensure compatibility.
	 *
	 *  @param {string} version Version string to check for, in the format "X.Y.Z".
	 *    Note that the formats "X" and "X.Y" are also acceptable.
	 *  @returns {boolean} true if this version of DataTables is greater or equal to
	 *    the required version, or false if this version of DataTales is not
	 *    suitable
	 *  @static
	 *  @dtopt API-Static
	 *
	 *  @example
	 *    alert( $.fn.dataTable.versionCheck( '1.9.0' ) );
	 */
	DataTable.versionCheck = DataTable.fnVersionCheck = function( version )
	{
		var aThis = DataTable.version.split('.');
		var aThat = version.split('.');
		var iThis, iThat;
	
		for ( var i=0, iLen=aThat.length ; i<iLen ; i++ ) {
			iThis = parseInt( aThis[i], 10 ) || 0;
			iThat = parseInt( aThat[i], 10 ) || 0;
	
			// Parts are the same, keep comparing
			if (iThis === iThat) {
				continue;
			}
	
			// Parts are different, return immediately
			return iThis > iThat;
		}
	
		return true;
	};
	
	
	/**
	 * Check if a `<table>` node is a DataTable table already or not.
	 *
	 *  @param {node|jquery|string} table Table node, jQuery object or jQuery
	 *      selector for the table to test. Note that if more than more than one
	 *      table is passed on, only the first will be checked
	 *  @returns {boolean} true the table given is a DataTable, or false otherwise
	 *  @static
	 *  @dtopt API-Static
	 *
	 *  @example
	 *    if ( ! $.fn.DataTable.isDataTable( '#example' ) ) {
	 *      $('#example').dataTable();
	 *    }
	 */
	DataTable.isDataTable = DataTable.fnIsDataTable = function ( table )
	{
		var t = $(table).get(0);
		var is = false;
	
		$.each( DataTable.settings, function (i, o) {
			var head = o.nScrollHead ? $('table', o.nScrollHead)[0] : null;
			var foot = o.nScrollFoot ? $('table', o.nScrollFoot)[0] : null;
	
			if ( o.nTable === t || head === t || foot === t ) {
				is = true;
			}
		} );
	
		return is;
	};
	
	
	/**
	 * Get all DataTable tables that have been initialised - optionally you can
	 * select to get only currently visible tables.
	 *
	 *  @param {boolean} [visible=false] Flag to indicate if you want all (default)
	 *    or visible tables only.
	 *  @returns {array} Array of `table` nodes (not DataTable instances) which are
	 *    DataTables
	 *  @static
	 *  @dtopt API-Static
	 *
	 *  @example
	 *    $.each( $.fn.dataTable.tables(true), function () {
	 *      $(table).DataTable().columns.adjust();
	 *    } );
	 */
	DataTable.tables = DataTable.fnTables = function ( visible )
	{
		var api = false;
	
		if ( $.isPlainObject( visible ) ) {
			api = visible.api;
			visible = visible.visible;
		}
	
		var a = $.map( DataTable.settings, function (o) {
			if ( !visible || (visible && $(o.nTable).is(':visible')) ) {
				return o.nTable;
			}
		} );
	
		return api ?
			new _Api( a ) :
			a;
	};
	
	
	/**
	 * Convert from camel case parameters to Hungarian notation. This is made public
	 * for the extensions to provide the same ability as DataTables core to accept
	 * either the 1.9 style Hungarian notation, or the 1.10+ style camelCase
	 * parameters.
	 *
	 *  @param {object} src The model object which holds all parameters that can be
	 *    mapped.
	 *  @param {object} user The object to convert from camel case to Hungarian.
	 *  @param {boolean} force When set to `true`, properties which already have a
	 *    Hungarian value in the `user` object will be overwritten. Otherwise they
	 *    won't be.
	 */
	DataTable.camelToHungarian = _fnCamelToHungarian;
	
	
	
	/**
	 *
	 */
	_api_register( '$()', function ( selector, opts ) {
		var
			rows   = this.rows( opts ).nodes(), // Get all rows
			jqRows = $(rows);
	
		return $( [].concat(
			jqRows.filter( selector ).toArray(),
			jqRows.find( selector ).toArray()
		) );
	} );
	
	
	// jQuery functions to operate on the tables
	$.each( [ 'on', 'one', 'off' ], function (i, key) {
		_api_register( key+'()', function ( /* event, handler */ ) {
			var args = Array.prototype.slice.call(arguments);
	
			// Add the `dt` namespace automatically if it isn't already present
			if ( ! args[0].match(/\.dt\b/) ) {
				args[0] += '.dt';
			}
	
			var inst = $( this.tables().nodes() );
			inst[key].apply( inst, args );
			return this;
		} );
	} );
	
	
	_api_register( 'clear()', function () {
		return this.iterator( 'table', function ( settings ) {
			_fnClearTable( settings );
		} );
	} );
	
	
	_api_register( 'settings()', function () {
		return new _Api( this.context, this.context );
	} );
	
	
	_api_register( 'init()', function () {
		var ctx = this.context;
		return ctx.length ? ctx[0].oInit : null;
	} );
	
	
	_api_register( 'data()', function () {
		return this.iterator( 'table', function ( settings ) {
			return _pluck( settings.aoData, '_aData' );
		} ).flatten();
	} );
	
	
	_api_register( 'destroy()', function ( remove ) {
		remove = remove || false;
	
		return this.iterator( 'table', function ( settings ) {
			var orig      = settings.nTableWrapper.parentNode;
			var classes   = settings.oClasses;
			var table     = settings.nTable;
			var tbody     = settings.nTBody;
			var thead     = settings.nTHead;
			var tfoot     = settings.nTFoot;
			var jqTable   = $(table);
			var jqTbody   = $(tbody);
			var jqWrapper = $(settings.nTableWrapper);
			var rows      = $.map( settings.aoData, function (r) { return r.nTr; } );
			var i, ien;
	
			// Flag to note that the table is currently being destroyed - no action
			// should be taken
			settings.bDestroying = true;
	
			// Fire off the destroy callbacks for plug-ins etc
			_fnCallbackFire( settings, "aoDestroyCallback", "destroy", [settings] );
	
			// If not being removed from the document, make all columns visible
			if ( ! remove ) {
				new _Api( settings ).columns().visible( true );
			}
	
			// Blitz all `DT` namespaced events (these are internal events, the
			// lowercase, `dt` events are user subscribed and they are responsible
			// for removing them
			jqWrapper.unbind('.DT').find(':not(tbody *)').unbind('.DT');
			$(window).unbind('.DT-'+settings.sInstance);
	
			// When scrolling we had to break the table up - restore it
			if ( table != thead.parentNode ) {
				jqTable.children('thead').detach();
				jqTable.append( thead );
			}
	
			if ( tfoot && table != tfoot.parentNode ) {
				jqTable.children('tfoot').detach();
				jqTable.append( tfoot );
			}
	
			settings.aaSorting = [];
			settings.aaSortingFixed = [];
			_fnSortingClasses( settings );
	
			$( rows ).removeClass( settings.asStripeClasses.join(' ') );
	
			$('th, td', thead).removeClass( classes.sSortable+' '+
				classes.sSortableAsc+' '+classes.sSortableDesc+' '+classes.sSortableNone
			);
	
			if ( settings.bJUI ) {
				$('th span.'+classes.sSortIcon+ ', td span.'+classes.sSortIcon, thead).detach();
				$('th, td', thead).each( function () {
					var wrapper = $('div.'+classes.sSortJUIWrapper, this);
					$(this).append( wrapper.contents() );
					wrapper.detach();
				} );
			}
	
			// Add the TR elements back into the table in their original order
			jqTbody.children().detach();
			jqTbody.append( rows );
	
			// Remove the DataTables generated nodes, events and classes
			var removedMethod = remove ? 'remove' : 'detach';
			jqTable[ removedMethod ]();
			jqWrapper[ removedMethod ]();
	
			// If we need to reattach the table to the document
			if ( ! remove && orig ) {
				// insertBefore acts like appendChild if !arg[1]
				orig.insertBefore( table, settings.nTableReinsertBefore );
	
				// Restore the width of the original table - was read from the style property,
				// so we can restore directly to that
				jqTable
					.css( 'width', settings.sDestroyWidth )
					.removeClass( classes.sTable );
	
				// If the were originally stripe classes - then we add them back here.
				// Note this is not fool proof (for example if not all rows had stripe
				// classes - but it's a good effort without getting carried away
				ien = settings.asDestroyStripes.length;
	
				if ( ien ) {
					jqTbody.children().each( function (i) {
						$(this).addClass( settings.asDestroyStripes[i % ien] );
					} );
				}
			}
	
			/* Remove the settings object from the settings array */
			var idx = $.inArray( settings, DataTable.settings );
			if ( idx !== -1 ) {
				DataTable.settings.splice( idx, 1 );
			}
		} );
	} );
	
	
	// Add the `every()` method for rows, columns and cells in a compact form
	$.each( [ 'column', 'row', 'cell' ], function ( i, type ) {
		_api_register( type+'s().every()', function ( fn ) {
			var opts = this.selector.opts;
			var api = this;
	
			return this.iterator( type, function ( settings, arg1, arg2, arg3, arg4 ) {
				// Rows and columns:
				//  arg1 - index
				//  arg2 - table counter
				//  arg3 - loop counter
				//  arg4 - undefined
				// Cells:
				//  arg1 - row index
				//  arg2 - column index
				//  arg3 - table counter
				//  arg4 - loop counter
				fn.call(
					api[ type ](
						arg1,
						type==='cell' ? arg2 : opts,
						type==='cell' ? opts : undefined
					),
					arg1, arg2, arg3, arg4
				);
			} );
		} );
	} );
	
	
	// i18n method for extensions to be able to use the language object from the
	// DataTable
	_api_register( 'i18n()', function ( token, def, plural ) {
		var ctx = this.context[0];
		var resolved = _fnGetObjectDataFn( token )( ctx.oLanguage );
	
		if ( resolved === undefined ) {
			resolved = def;
		}
	
		if ( plural !== undefined && $.isPlainObject( resolved ) ) {
			resolved = resolved[ plural ] !== undefined ?
				resolved[ plural ] :
				resolved._;
		}
	
		return resolved.replace( '%d', plural ); // nb: plural might be undefined,
	} );

	/**
	 * Version string for plug-ins to check compatibility. Allowed format is
	 * `a.b.c-d` where: a:int, b:int, c:int, d:string(dev|beta|alpha). `d` is used
	 * only for non-release builds. See http://semver.org/ for more information.
	 *  @member
	 *  @type string
	 *  @default Version number
	 */
	DataTable.version = "1.10.12";

	/**
	 * Private data store, containing all of the settings objects that are
	 * created for the tables on a given page.
	 *
	 * Note that the `DataTable.settings` object is aliased to
	 * `jQuery.fn.dataTableExt` through which it may be accessed and
	 * manipulated, or `jQuery.fn.dataTable.settings`.
	 *  @member
	 *  @type array
	 *  @default []
	 *  @private
	 */
	DataTable.settings = [];

	/**
	 * Object models container, for the various models that DataTables has
	 * available to it. These models define the objects that are used to hold
	 * the active state and configuration of the table.
	 *  @namespace
	 */
	DataTable.models = {};
	
	
	
	/**
	 * Template object for the way in which DataTables holds information about
	 * search information for the global filter and individual column filters.
	 *  @namespace
	 */
	DataTable.models.oSearch = {
		/**
		 * Flag to indicate if the filtering should be case insensitive or not
		 *  @type boolean
		 *  @default true
		 */
		"bCaseInsensitive": true,
	
		/**
		 * Applied search term
		 *  @type string
		 *  @default <i>Empty string</i>
		 */
		"sSearch": "",
	
		/**
		 * Flag to indicate if the search term should be interpreted as a
		 * regular expression (true) or not (false) and therefore and special
		 * regex characters escaped.
		 *  @type boolean
		 *  @default false
		 */
		"bRegex": false,
	
		/**
		 * Flag to indicate if DataTables is to use its smart filtering or not.
		 *  @type boolean
		 *  @default true
		 */
		"bSmart": true
	};
	
	
	
	
	/**
	 * Template object for the way in which DataTables holds information about
	 * each individual row. This is the object format used for the settings
	 * aoData array.
	 *  @namespace
	 */
	DataTable.models.oRow = {
		/**
		 * TR element for the row
		 *  @type node
		 *  @default null
		 */
		"nTr": null,
	
		/**
		 * Array of TD elements for each row. This is null until the row has been
		 * created.
		 *  @type array nodes
		 *  @default []
		 */
		"anCells": null,
	
		/**
		 * Data object from the original data source for the row. This is either
		 * an array if using the traditional form of DataTables, or an object if
		 * using mData options. The exact type will depend on the passed in
		 * data from the data source, or will be an array if using DOM a data
		 * source.
		 *  @type array|object
		 *  @default []
		 */
		"_aData": [],
	
		/**
		 * Sorting data cache - this array is ostensibly the same length as the
		 * number of columns (although each index is generated only as it is
		 * needed), and holds the data that is used for sorting each column in the
		 * row. We do this cache generation at the start of the sort in order that
		 * the formatting of the sort data need be done only once for each cell
		 * per sort. This array should not be read from or written to by anything
		 * other than the master sorting methods.
		 *  @type array
		 *  @default null
		 *  @private
		 */
		"_aSortData": null,
	
		/**
		 * Per cell filtering data cache. As per the sort data cache, used to
		 * increase the performance of the filtering in DataTables
		 *  @type array
		 *  @default null
		 *  @private
		 */
		"_aFilterData": null,
	
		/**
		 * Filtering data cache. This is the same as the cell filtering cache, but
		 * in this case a string rather than an array. This is easily computed with
		 * a join on `_aFilterData`, but is provided as a cache so the join isn't
		 * needed on every search (memory traded for performance)
		 *  @type array
		 *  @default null
		 *  @private
		 */
		"_sFilterRow": null,
	
		/**
		 * Cache of the class name that DataTables has applied to the row, so we
		 * can quickly look at this variable rather than needing to do a DOM check
		 * on className for the nTr property.
		 *  @type string
		 *  @default <i>Empty string</i>
		 *  @private
		 */
		"_sRowStripe": "",
	
		/**
		 * Denote if the original data source was from the DOM, or the data source
		 * object. This is used for invalidating data, so DataTables can
		 * automatically read data from the original source, unless uninstructed
		 * otherwise.
		 *  @type string
		 *  @default null
		 *  @private
		 */
		"src": null,
	
		/**
		 * Index in the aoData array. This saves an indexOf lookup when we have the
		 * object, but want to know the index
		 *  @type integer
		 *  @default -1
		 *  @private
		 */
		"idx": -1
	};
	
	
	/**
	 * Template object for the column information object in DataTables. This object
	 * is held in the settings aoColumns array and contains all the information that
	 * DataTables needs about each individual column.
	 *
	 * Note that this object is related to {@link DataTable.defaults.column}
	 * but this one is the internal data store for DataTables's cache of columns.
	 * It should NOT be manipulated outside of DataTables. Any configuration should
	 * be done through the initialisation options.
	 *  @namespace
	 */
	DataTable.models.oColumn = {
		/**
		 * Column index. This could be worked out on-the-fly with $.inArray, but it
		 * is faster to just hold it as a variable
		 *  @type integer
		 *  @default null
		 */
		"idx": null,
	
		/**
		 * A list of the columns that sorting should occur on when this column
		 * is sorted. That this property is an array allows multi-column sorting
		 * to be defined for a column (for example first name / last name columns
		 * would benefit from this). The values are integers pointing to the
		 * columns to be sorted on (typically it will be a single integer pointing
		 * at itself, but that doesn't need to be the case).
		 *  @type array
		 */
		"aDataSort": null,
	
		/**
		 * Define the sorting directions that are applied to the column, in sequence
		 * as the column is repeatedly sorted upon - i.e. the first value is used
		 * as the sorting direction when the column if first sorted (clicked on).
		 * Sort it again (click again) and it will move on to the next index.
		 * Repeat until loop.
		 *  @type array
		 */
		"asSorting": null,
	
		/**
		 * Flag to indicate if the column is searchable, and thus should be included
		 * in the filtering or not.
		 *  @type boolean
		 */
		"bSearchable": null,
	
		/**
		 * Flag to indicate if the column is sortable or not.
		 *  @type boolean
		 */
		"bSortable": null,
	
		/**
		 * Flag to indicate if the column is currently visible in the table or not
		 *  @type boolean
		 */
		"bVisible": null,
	
		/**
		 * Store for manual type assignment using the `column.type` option. This
		 * is held in store so we can manipulate the column's `sType` property.
		 *  @type string
		 *  @default null
		 *  @private
		 */
		"_sManualType": null,
	
		/**
		 * Flag to indicate if HTML5 data attributes should be used as the data
		 * source for filtering or sorting. True is either are.
		 *  @type boolean
		 *  @default false
		 *  @private
		 */
		"_bAttrSrc": false,
	
		/**
		 * Developer definable function that is called whenever a cell is created (Ajax source,
		 * etc) or processed for input (DOM source). This can be used as a compliment to mRender
		 * allowing you to modify the DOM element (add background colour for example) when the
		 * element is available.
		 *  @type function
		 *  @param {element} nTd The TD node that has been created
		 *  @param {*} sData The Data for the cell
		 *  @param {array|object} oData The data for the whole row
		 *  @param {int} iRow The row index for the aoData data store
		 *  @default null
		 */
		"fnCreatedCell": null,
	
		/**
		 * Function to get data from a cell in a column. You should <b>never</b>
		 * access data directly through _aData internally in DataTables - always use
		 * the method attached to this property. It allows mData to function as
		 * required. This function is automatically assigned by the column
		 * initialisation method
		 *  @type function
		 *  @param {array|object} oData The data array/object for the array
		 *    (i.e. aoData[]._aData)
		 *  @param {string} sSpecific The specific data type you want to get -
		 *    'display', 'type' 'filter' 'sort'
		 *  @returns {*} The data for the cell from the given row's data
		 *  @default null
		 */
		"fnGetData": null,
	
		/**
		 * Function to set data for a cell in the column. You should <b>never</b>
		 * set the data directly to _aData internally in DataTables - always use
		 * this method. It allows mData to function as required. This function
		 * is automatically assigned by the column initialisation method
		 *  @type function
		 *  @param {array|object} oData The data array/object for the array
		 *    (i.e. aoData[]._aData)
		 *  @param {*} sValue Value to set
		 *  @default null
		 */
		"fnSetData": null,
	
		/**
		 * Property to read the value for the cells in the column from the data
		 * source array / object. If null, then the default content is used, if a
		 * function is given then the return from the function is used.
		 *  @type function|int|string|null
		 *  @default null
		 */
		"mData": null,
	
		/**
		 * Partner property to mData which is used (only when defined) to get
		 * the data - i.e. it is basically the same as mData, but without the
		 * 'set' option, and also the data fed to it is the result from mData.
		 * This is the rendering method to match the data method of mData.
		 *  @type function|int|string|null
		 *  @default null
		 */
		"mRender": null,
	
		/**
		 * Unique header TH/TD element for this column - this is what the sorting
		 * listener is attached to (if sorting is enabled.)
		 *  @type node
		 *  @default null
		 */
		"nTh": null,
	
		/**
		 * Unique footer TH/TD element for this column (if there is one). Not used
		 * in DataTables as such, but can be used for plug-ins to reference the
		 * footer for each column.
		 *  @type node
		 *  @default null
		 */
		"nTf": null,
	
		/**
		 * The class to apply to all TD elements in the table's TBODY for the column
		 *  @type string
		 *  @default null
		 */
		"sClass": null,
	
		/**
		 * When DataTables calculates the column widths to assign to each column,
		 * it finds the longest string in each column and then constructs a
		 * temporary table and reads the widths from that. The problem with this
		 * is that "mmm" is much wider then "iiii", but the latter is a longer
		 * string - thus the calculation can go wrong (doing it properly and putting
		 * it into an DOM object and measuring that is horribly(!) slow). Thus as
		 * a "work around" we provide this option. It will append its value to the
		 * text that is found to be the longest string for the column - i.e. padding.
		 *  @type string
		 */
		"sContentPadding": null,
	
		/**
		 * Allows a default value to be given for a column's data, and will be used
		 * whenever a null data source is encountered (this can be because mData
		 * is set to null, or because the data source itself is null).
		 *  @type string
		 *  @default null
		 */
		"sDefaultContent": null,
	
		/**
		 * Name for the column, allowing reference to the column by name as well as
		 * by index (needs a lookup to work by name).
		 *  @type string
		 */
		"sName": null,
	
		/**
		 * Custom sorting data type - defines which of the available plug-ins in
		 * afnSortData the custom sorting will use - if any is defined.
		 *  @type string
		 *  @default std
		 */
		"sSortDataType": 'std',
	
		/**
		 * Class to be applied to the header element when sorting on this column
		 *  @type string
		 *  @default null
		 */
		"sSortingClass": null,
	
		/**
		 * Class to be applied to the header element when sorting on this column -
		 * when jQuery UI theming is used.
		 *  @type string
		 *  @default null
		 */
		"sSortingClassJUI": null,
	
		/**
		 * Title of the column - what is seen in the TH element (nTh).
		 *  @type string
		 */
		"sTitle": null,
	
		/**
		 * Column sorting and filtering type
		 *  @type string
		 *  @default null
		 */
		"sType": null,
	
		/**
		 * Width of the column
		 *  @type string
		 *  @default null
		 */
		"sWidth": null,
	
		/**
		 * Width of the column when it was first "encountered"
		 *  @type string
		 *  @default null
		 */
		"sWidthOrig": null
	};
	
	
	/*
	 * Developer note: The properties of the object below are given in Hungarian
	 * notation, that was used as the interface for DataTables prior to v1.10, however
	 * from v1.10 onwards the primary interface is camel case. In order to avoid
	 * breaking backwards compatibility utterly with this change, the Hungarian
	 * version is still, internally the primary interface, but is is not documented
	 * - hence the @name tags in each doc comment. This allows a Javascript function
	 * to create a map from Hungarian notation to camel case (going the other direction
	 * would require each property to be listed, which would at around 3K to the size
	 * of DataTables, while this method is about a 0.5K hit.
	 *
	 * Ultimately this does pave the way for Hungarian notation to be dropped
	 * completely, but that is a massive amount of work and will break current
	 * installs (therefore is on-hold until v2).
	 */
	
	/**
	 * Initialisation options that can be given to DataTables at initialisation
	 * time.
	 *  @namespace
	 */
	DataTable.defaults = {
		/**
		 * An array of data to use for the table, passed in at initialisation which
		 * will be used in preference to any data which is already in the DOM. This is
		 * particularly useful for constructing tables purely in Javascript, for
		 * example with a custom Ajax call.
		 *  @type array
		 *  @default null
		 *
		 *  @dtopt Option
		 *  @name DataTable.defaults.data
		 *
		 *  @example
		 *    // Using a 2D array data source
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "data": [
		 *          ['Trident', 'Internet Explorer 4.0', 'Win 95+', 4, 'X'],
		 *          ['Trident', 'Internet Explorer 5.0', 'Win 95+', 5, 'C'],
		 *        ],
		 *        "columns": [
		 *          { "title": "Engine" },
		 *          { "title": "Browser" },
		 *          { "title": "Platform" },
		 *          { "title": "Version" },
		 *          { "title": "Grade" }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using an array of objects as a data source (`data`)
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "data": [
		 *          {
		 *            "engine":   "Trident",
		 *            "browser":  "Internet Explorer 4.0",
		 *            "platform": "Win 95+",
		 *            "version":  4,
		 *            "grade":    "X"
		 *          },
		 *          {
		 *            "engine":   "Trident",
		 *            "browser":  "Internet Explorer 5.0",
		 *            "platform": "Win 95+",
		 *            "version":  5,
		 *            "grade":    "C"
		 *          }
		 *        ],
		 *        "columns": [
		 *          { "title": "Engine",   "data": "engine" },
		 *          { "title": "Browser",  "data": "browser" },
		 *          { "title": "Platform", "data": "platform" },
		 *          { "title": "Version",  "data": "version" },
		 *          { "title": "Grade",    "data": "grade" }
		 *        ]
		 *      } );
		 *    } );
		 */
		"aaData": null,
	
	
		/**
		 * If ordering is enabled, then DataTables will perform a first pass sort on
		 * initialisation. You can define which column(s) the sort is performed
		 * upon, and the sorting direction, with this variable. The `sorting` array
		 * should contain an array for each column to be sorted initially containing
		 * the column's index and a direction string ('asc' or 'desc').
		 *  @type array
		 *  @default [[0,'asc']]
		 *
		 *  @dtopt Option
		 *  @name DataTable.defaults.order
		 *
		 *  @example
		 *    // Sort by 3rd column first, and then 4th column
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "order": [[2,'asc'], [3,'desc']]
		 *      } );
		 *    } );
		 *
		 *    // No initial sorting
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "order": []
		 *      } );
		 *    } );
		 */
		"aaSorting": [[0,'asc']],
	
	
		/**
		 * This parameter is basically identical to the `sorting` parameter, but
		 * cannot be overridden by user interaction with the table. What this means
		 * is that you could have a column (visible or hidden) which the sorting
		 * will always be forced on first - any sorting after that (from the user)
		 * will then be performed as required. This can be useful for grouping rows
		 * together.
		 *  @type array
		 *  @default null
		 *
		 *  @dtopt Option
		 *  @name DataTable.defaults.orderFixed
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "orderFixed": [[0,'asc']]
		 *      } );
		 *    } )
		 */
		"aaSortingFixed": [],
	
	
		/**
		 * DataTables can be instructed to load data to display in the table from a
		 * Ajax source. This option defines how that Ajax call is made and where to.
		 *
		 * The `ajax` property has three different modes of operation, depending on
		 * how it is defined. These are:
		 *
		 * * `string` - Set the URL from where the data should be loaded from.
		 * * `object` - Define properties for `jQuery.ajax`.
		 * * `function` - Custom data get function
		 *
		 * `string`
		 * --------
		 *
		 * As a string, the `ajax` property simply defines the URL from which
		 * DataTables will load data.
		 *
		 * `object`
		 * --------
		 *
		 * As an object, the parameters in the object are passed to
		 * [jQuery.ajax](http://api.jquery.com/jQuery.ajax/) allowing fine control
		 * of the Ajax request. DataTables has a number of default parameters which
		 * you can override using this option. Please refer to the jQuery
		 * documentation for a full description of the options available, although
		 * the following parameters provide additional options in DataTables or
		 * require special consideration:
		 *
		 * * `data` - As with jQuery, `data` can be provided as an object, but it
		 *   can also be used as a function to manipulate the data DataTables sends
		 *   to the server. The function takes a single parameter, an object of
		 *   parameters with the values that DataTables has readied for sending. An
		 *   object may be returned which will be merged into the DataTables
		 *   defaults, or you can add the items to the object that was passed in and
		 *   not return anything from the function. This supersedes `fnServerParams`
		 *   from DataTables 1.9-.
		 *
		 * * `dataSrc` - By default DataTables will look for the property `data` (or
		 *   `aaData` for compatibility with DataTables 1.9-) when obtaining data
		 *   from an Ajax source or for server-side processing - this parameter
		 *   allows that property to be changed. You can use Javascript dotted
		 *   object notation to get a data source for multiple levels of nesting, or
		 *   it my be used as a function. As a function it takes a single parameter,
		 *   the JSON returned from the server, which can be manipulated as
		 *   required, with the returned value being that used by DataTables as the
		 *   data source for the table. This supersedes `sAjaxDataProp` from
		 *   DataTables 1.9-.
		 *
		 * * `success` - Should not be overridden it is used internally in
		 *   DataTables. To manipulate / transform the data returned by the server
		 *   use `ajax.dataSrc`, or use `ajax` as a function (see below).
		 *
		 * `function`
		 * ----------
		 *
		 * As a function, making the Ajax call is left up to yourself allowing
		 * complete control of the Ajax request. Indeed, if desired, a method other
		 * than Ajax could be used to obtain the required data, such as Web storage
		 * or an AIR database.
		 *
		 * The function is given four parameters and no return is required. The
		 * parameters are:
		 *
		 * 1. _object_ - Data to send to the server
		 * 2. _function_ - Callback function that must be executed when the required
		 *    data has been obtained. That data should be passed into the callback
		 *    as the only parameter
		 * 3. _object_ - DataTables settings object for the table
		 *
		 * Note that this supersedes `fnServerData` from DataTables 1.9-.
		 *
		 *  @type string|object|function
		 *  @default null
		 *
		 *  @dtopt Option
		 *  @name DataTable.defaults.ajax
		 *  @since 1.10.0
		 *
		 * @example
		 *   // Get JSON data from a file via Ajax.
		 *   // Note DataTables expects data in the form `{ data: [ ...data... ] }` by default).
		 *   $('#example').dataTable( {
		 *     "ajax": "data.json"
		 *   } );
		 *
		 * @example
		 *   // Get JSON data from a file via Ajax, using `dataSrc` to change
		 *   // `data` to `tableData` (i.e. `{ tableData: [ ...data... ] }`)
		 *   $('#example').dataTable( {
		 *     "ajax": {
		 *       "url": "data.json",
		 *       "dataSrc": "tableData"
		 *     }
		 *   } );
		 *
		 * @example
		 *   // Get JSON data from a file via Ajax, using `dataSrc` to read data
		 *   // from a plain array rather than an array in an object
		 *   $('#example').dataTable( {
		 *     "ajax": {
		 *       "url": "data.json",
		 *       "dataSrc": ""
		 *     }
		 *   } );
		 *
		 * @example
		 *   // Manipulate the data returned from the server - add a link to data
		 *   // (note this can, should, be done using `render` for the column - this
		 *   // is just a simple example of how the data can be manipulated).
		 *   $('#example').dataTable( {
		 *     "ajax": {
		 *       "url": "data.json",
		 *       "dataSrc": function ( json ) {
		 *         for ( var i=0, ien=json.length ; i<ien ; i++ ) {
		 *           json[i][0] = '<a href="/message/'+json[i][0]+'>View message</a>';
		 *         }
		 *         return json;
		 *       }
		 *     }
		 *   } );
		 *
		 * @example
		 *   // Add data to the request
		 *   $('#example').dataTable( {
		 *     "ajax": {
		 *       "url": "data.json",
		 *       "data": function ( d ) {
		 *         return {
		 *           "extra_search": $('#extra').val()
		 *         };
		 *       }
		 *     }
		 *   } );
		 *
		 * @example
		 *   // Send request as POST
		 *   $('#example').dataTable( {
		 *     "ajax": {
		 *       "url": "data.json",
		 *       "type": "POST"
		 *     }
		 *   } );
		 *
		 * @example
		 *   // Get the data from localStorage (could interface with a form for
		 *   // adding, editing and removing rows).
		 *   $('#example').dataTable( {
		 *     "ajax": function (data, callback, settings) {
		 *       callback(
		 *         JSON.parse( localStorage.getItem('dataTablesData') )
		 *       );
		 *     }
		 *   } );
		 */
		"ajax": null,
	
	
		/**
		 * This parameter allows you to readily specify the entries in the length drop
		 * down menu that DataTables shows when pagination is enabled. It can be
		 * either a 1D array of options which will be used for both the displayed
		 * option and the value, or a 2D array which will use the array in the first
		 * position as the value, and the array in the second position as the
		 * displayed options (useful for language strings such as 'All').
		 *
		 * Note that the `pageLength` property will be automatically set to the
		 * first value given in this array, unless `pageLength` is also provided.
		 *  @type array
		 *  @default [ 10, 25, 50, 100 ]
		 *
		 *  @dtopt Option
		 *  @name DataTable.defaults.lengthMenu
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "lengthMenu": [[10, 25, 50, -1], [10, 25, 50, "All"]]
		 *      } );
		 *    } );
		 */
		"aLengthMenu": [ 10, 25, 50, 100 ],
	
	
		/**
		 * The `columns` option in the initialisation parameter allows you to define
		 * details about the way individual columns behave. For a full list of
		 * column options that can be set, please see
		 * {@link DataTable.defaults.column}. Note that if you use `columns` to
		 * define your columns, you must have an entry in the array for every single
		 * column that you have in your table (these can be null if you don't which
		 * to specify any options).
		 *  @member
		 *
		 *  @name DataTable.defaults.column
		 */
		"aoColumns": null,
	
		/**
		 * Very similar to `columns`, `columnDefs` allows you to target a specific
		 * column, multiple columns, or all columns, using the `targets` property of
		 * each object in the array. This allows great flexibility when creating
		 * tables, as the `columnDefs` arrays can be of any length, targeting the
		 * columns you specifically want. `columnDefs` may use any of the column
		 * options available: {@link DataTable.defaults.column}, but it _must_
		 * have `targets` defined in each object in the array. Values in the `targets`
		 * array may be:
		 *   <ul>
		 *     <li>a string - class name will be matched on the TH for the column</li>
		 *     <li>0 or a positive integer - column index counting from the left</li>
		 *     <li>a negative integer - column index counting from the right</li>
		 *     <li>the string "_all" - all columns (i.e. assign a default)</li>
		 *   </ul>
		 *  @member
		 *
		 *  @name DataTable.defaults.columnDefs
		 */
		"aoColumnDefs": null,
	
	
		/**
		 * Basically the same as `search`, this parameter defines the individual column
		 * filtering state at initialisation time. The array must be of the same size
		 * as the number of columns, and each element be an object with the parameters
		 * `search` and `escapeRegex` (the latter is optional). 'null' is also
		 * accepted and the default will be used.
		 *  @type array
		 *  @default []
		 *
		 *  @dtopt Option
		 *  @name DataTable.defaults.searchCols
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "searchCols": [
		 *          null,
		 *          { "search": "My filter" },
		 *          null,
		 *          { "search": "^[0-9]", "escapeRegex": false }
		 *        ]
		 *      } );
		 *    } )
		 */
		"aoSearchCols": [],
	
	
		/**
		 * An array of CSS classes that should be applied to displayed rows. This
		 * array may be of any length, and DataTables will apply each class
		 * sequentially, looping when required.
		 *  @type array
		 *  @default null <i>Will take the values determined by the `oClasses.stripe*`
		 *    options</i>
		 *
		 *  @dtopt Option
		 *  @name DataTable.defaults.stripeClasses
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "stripeClasses": [ 'strip1', 'strip2', 'strip3' ]
		 *      } );
		 *    } )
		 */
		"asStripeClasses": null,
	
	
		/**
		 * Enable or disable automatic column width calculation. This can be disabled
		 * as an optimisation (it takes some time to calculate the widths) if the
		 * tables widths are passed in using `columns`.
		 *  @type boolean
		 *  @default true
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.autoWidth
		 *
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "autoWidth": false
		 *      } );
		 *    } );
		 */
		"bAutoWidth": true,
	
	
		/**
		 * Deferred rendering can provide DataTables with a huge speed boost when you
		 * are using an Ajax or JS data source for the table. This option, when set to
		 * true, will cause DataTables to defer the creation of the table elements for
		 * each row until they are needed for a draw - saving a significant amount of
		 * time.
		 *  @type boolean
		 *  @default false
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.deferRender
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "ajax": "sources/arrays.txt",
		 *        "deferRender": true
		 *      } );
		 *    } );
		 */
		"bDeferRender": false,
	
	
		/**
		 * Replace a DataTable which matches the given selector and replace it with
		 * one which has the properties of the new initialisation object passed. If no
		 * table matches the selector, then the new DataTable will be constructed as
		 * per normal.
		 *  @type boolean
		 *  @default false
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.destroy
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "srollY": "200px",
		 *        "paginate": false
		 *      } );
		 *
		 *      // Some time later....
		 *      $('#example').dataTable( {
		 *        "filter": false,
		 *        "destroy": true
		 *      } );
		 *    } );
		 */
		"bDestroy": false,
	
	
		/**
		 * Enable or disable filtering of data. Filtering in DataTables is "smart" in
		 * that it allows the end user to input multiple words (space separated) and
		 * will match a row containing those words, even if not in the order that was
		 * specified (this allow matching across multiple columns). Note that if you
		 * wish to use filtering in DataTables this must remain 'true' - to remove the
		 * default filtering input box and retain filtering abilities, please use
		 * {@link DataTable.defaults.dom}.
		 *  @type boolean
		 *  @default true
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.searching
		 *
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "searching": false
		 *      } );
		 *    } );
		 */
		"bFilter": true,
	
	
		/**
		 * Enable or disable the table information display. This shows information
		 * about the data that is currently visible on the page, including information
		 * about filtered data if that action is being performed.
		 *  @type boolean
		 *  @default true
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.info
		 *
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "info": false
		 *      } );
		 *    } );
		 */
		"bInfo": true,
	
	
		/**
		 * Enable jQuery UI ThemeRoller support (required as ThemeRoller requires some
		 * slightly different and additional mark-up from what DataTables has
		 * traditionally used).
		 *  @type boolean
		 *  @default false
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.jQueryUI
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "jQueryUI": true
		 *      } );
		 *    } );
		 */
		"bJQueryUI": false,
	
	
		/**
		 * Allows the end user to select the size of a formatted page from a select
		 * menu (sizes are 10, 25, 50 and 100). Requires pagination (`paginate`).
		 *  @type boolean
		 *  @default true
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.lengthChange
		 *
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "lengthChange": false
		 *      } );
		 *    } );
		 */
		"bLengthChange": true,
	
	
		/**
		 * Enable or disable pagination.
		 *  @type boolean
		 *  @default true
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.paging
		 *
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "paging": false
		 *      } );
		 *    } );
		 */
		"bPaginate": true,
	
	
		/**
		 * Enable or disable the display of a 'processing' indicator when the table is
		 * being processed (e.g. a sort). This is particularly useful for tables with
		 * large amounts of data where it can take a noticeable amount of time to sort
		 * the entries.
		 *  @type boolean
		 *  @default false
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.processing
		 *
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "processing": true
		 *      } );
		 *    } );
		 */
		"bProcessing": false,
	
	
		/**
		 * Retrieve the DataTables object for the given selector. Note that if the
		 * table has already been initialised, this parameter will cause DataTables
		 * to simply return the object that has already been set up - it will not take
		 * account of any changes you might have made to the initialisation object
		 * passed to DataTables (setting this parameter to true is an acknowledgement
		 * that you understand this). `destroy` can be used to reinitialise a table if
		 * you need.
		 *  @type boolean
		 *  @default false
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.retrieve
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      initTable();
		 *      tableActions();
		 *    } );
		 *
		 *    function initTable ()
		 *    {
		 *      return $('#example').dataTable( {
		 *        "scrollY": "200px",
		 *        "paginate": false,
		 *        "retrieve": true
		 *      } );
		 *    }
		 *
		 *    function tableActions ()
		 *    {
		 *      var table = initTable();
		 *      // perform API operations with oTable
		 *    }
		 */
		"bRetrieve": false,
	
	
		/**
		 * When vertical (y) scrolling is enabled, DataTables will force the height of
		 * the table's viewport to the given height at all times (useful for layout).
		 * However, this can look odd when filtering data down to a small data set,
		 * and the footer is left "floating" further down. This parameter (when
		 * enabled) will cause DataTables to collapse the table's viewport down when
		 * the result set will fit within the given Y height.
		 *  @type boolean
		 *  @default false
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.scrollCollapse
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "scrollY": "200",
		 *        "scrollCollapse": true
		 *      } );
		 *    } );
		 */
		"bScrollCollapse": false,
	
	
		/**
		 * Configure DataTables to use server-side processing. Note that the
		 * `ajax` parameter must also be given in order to give DataTables a
		 * source to obtain the required data for each draw.
		 *  @type boolean
		 *  @default false
		 *
		 *  @dtopt Features
		 *  @dtopt Server-side
		 *  @name DataTable.defaults.serverSide
		 *
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "serverSide": true,
		 *        "ajax": "xhr.php"
		 *      } );
		 *    } );
		 */
		"bServerSide": false,
	
	
		/**
		 * Enable or disable sorting of columns. Sorting of individual columns can be
		 * disabled by the `sortable` option for each column.
		 *  @type boolean
		 *  @default true
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.ordering
		 *
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "ordering": false
		 *      } );
		 *    } );
		 */
		"bSort": true,
	
	
		/**
		 * Enable or display DataTables' ability to sort multiple columns at the
		 * same time (activated by shift-click by the user).
		 *  @type boolean
		 *  @default true
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.orderMulti
		 *
		 *  @example
		 *    // Disable multiple column sorting ability
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "orderMulti": false
		 *      } );
		 *    } );
		 */
		"bSortMulti": true,
	
	
		/**
		 * Allows control over whether DataTables should use the top (true) unique
		 * cell that is found for a single column, or the bottom (false - default).
		 * This is useful when using complex headers.
		 *  @type boolean
		 *  @default false
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.orderCellsTop
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "orderCellsTop": true
		 *      } );
		 *    } );
		 */
		"bSortCellsTop": false,
	
	
		/**
		 * Enable or disable the addition of the classes `sorting\_1`, `sorting\_2` and
		 * `sorting\_3` to the columns which are currently being sorted on. This is
		 * presented as a feature switch as it can increase processing time (while
		 * classes are removed and added) so for large data sets you might want to
		 * turn this off.
		 *  @type boolean
		 *  @default true
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.orderClasses
		 *
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "orderClasses": false
		 *      } );
		 *    } );
		 */
		"bSortClasses": true,
	
	
		/**
		 * Enable or disable state saving. When enabled HTML5 `localStorage` will be
		 * used to save table display information such as pagination information,
		 * display length, filtering and sorting. As such when the end user reloads
		 * the page the display display will match what thy had previously set up.
		 *
		 * Due to the use of `localStorage` the default state saving is not supported
		 * in IE6 or 7. If state saving is required in those browsers, use
		 * `stateSaveCallback` to provide a storage solution such as cookies.
		 *  @type boolean
		 *  @default false
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.stateSave
		 *
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "stateSave": true
		 *      } );
		 *    } );
		 */
		"bStateSave": false,
	
	
		/**
		 * This function is called when a TR element is created (and all TD child
		 * elements have been inserted), or registered if using a DOM source, allowing
		 * manipulation of the TR element (adding classes etc).
		 *  @type function
		 *  @param {node} row "TR" element for the current row
		 *  @param {array} data Raw data array for this row
		 *  @param {int} dataIndex The index of this row in the internal aoData array
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.createdRow
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "createdRow": function( row, data, dataIndex ) {
		 *          // Bold the grade for all 'A' grade browsers
		 *          if ( data[4] == "A" )
		 *          {
		 *            $('td:eq(4)', row).html( '<b>A</b>' );
		 *          }
		 *        }
		 *      } );
		 *    } );
		 */
		"fnCreatedRow": null,
	
	
		/**
		 * This function is called on every 'draw' event, and allows you to
		 * dynamically modify any aspect you want about the created DOM.
		 *  @type function
		 *  @param {object} settings DataTables settings object
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.drawCallback
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "drawCallback": function( settings ) {
		 *          alert( 'DataTables has redrawn the table' );
		 *        }
		 *      } );
		 *    } );
		 */
		"fnDrawCallback": null,
	
	
		/**
		 * Identical to fnHeaderCallback() but for the table footer this function
		 * allows you to modify the table footer on every 'draw' event.
		 *  @type function
		 *  @param {node} foot "TR" element for the footer
		 *  @param {array} data Full table data (as derived from the original HTML)
		 *  @param {int} start Index for the current display starting point in the
		 *    display array
		 *  @param {int} end Index for the current display ending point in the
		 *    display array
		 *  @param {array int} display Index array to translate the visual position
		 *    to the full data array
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.footerCallback
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "footerCallback": function( tfoot, data, start, end, display ) {
		 *          tfoot.getElementsByTagName('th')[0].innerHTML = "Starting index is "+start;
		 *        }
		 *      } );
		 *    } )
		 */
		"fnFooterCallback": null,
	
	
		/**
		 * When rendering large numbers in the information element for the table
		 * (i.e. "Showing 1 to 10 of 57 entries") DataTables will render large numbers
		 * to have a comma separator for the 'thousands' units (e.g. 1 million is
		 * rendered as "1,000,000") to help readability for the end user. This
		 * function will override the default method DataTables uses.
		 *  @type function
		 *  @member
		 *  @param {int} toFormat number to be formatted
		 *  @returns {string} formatted string for DataTables to show the number
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.formatNumber
		 *
		 *  @example
		 *    // Format a number using a single quote for the separator (note that
		 *    // this can also be done with the language.thousands option)
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "formatNumber": function ( toFormat ) {
		 *          return toFormat.toString().replace(
		 *            /\B(?=(\d{3})+(?!\d))/g, "'"
		 *          );
		 *        };
		 *      } );
		 *    } );
		 */
		"fnFormatNumber": function ( toFormat ) {
			return toFormat.toString().replace(
				/\B(?=(\d{3})+(?!\d))/g,
				this.oLanguage.sThousands
			);
		},
	
	
		/**
		 * This function is called on every 'draw' event, and allows you to
		 * dynamically modify the header row. This can be used to calculate and
		 * display useful information about the table.
		 *  @type function
		 *  @param {node} head "TR" element for the header
		 *  @param {array} data Full table data (as derived from the original HTML)
		 *  @param {int} start Index for the current display starting point in the
		 *    display array
		 *  @param {int} end Index for the current display ending point in the
		 *    display array
		 *  @param {array int} display Index array to translate the visual position
		 *    to the full data array
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.headerCallback
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "fheaderCallback": function( head, data, start, end, display ) {
		 *          head.getElementsByTagName('th')[0].innerHTML = "Displaying "+(end-start)+" records";
		 *        }
		 *      } );
		 *    } )
		 */
		"fnHeaderCallback": null,
	
	
		/**
		 * The information element can be used to convey information about the current
		 * state of the table. Although the internationalisation options presented by
		 * DataTables are quite capable of dealing with most customisations, there may
		 * be times where you wish to customise the string further. This callback
		 * allows you to do exactly that.
		 *  @type function
		 *  @param {object} oSettings DataTables settings object
		 *  @param {int} start Starting position in data for the draw
		 *  @param {int} end End position in data for the draw
		 *  @param {int} max Total number of rows in the table (regardless of
		 *    filtering)
		 *  @param {int} total Total number of rows in the data set, after filtering
		 *  @param {string} pre The string that DataTables has formatted using it's
		 *    own rules
		 *  @returns {string} The string to be displayed in the information element.
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.infoCallback
		 *
		 *  @example
		 *    $('#example').dataTable( {
		 *      "infoCallback": function( settings, start, end, max, total, pre ) {
		 *        return start +" to "+ end;
		 *      }
		 *    } );
		 */
		"fnInfoCallback": null,
	
	
		/**
		 * Called when the table has been initialised. Normally DataTables will
		 * initialise sequentially and there will be no need for this function,
		 * however, this does not hold true when using external language information
		 * since that is obtained using an async XHR call.
		 *  @type function
		 *  @param {object} settings DataTables settings object
		 *  @param {object} json The JSON object request from the server - only
		 *    present if client-side Ajax sourced data is used
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.initComplete
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "initComplete": function(settings, json) {
		 *          alert( 'DataTables has finished its initialisation.' );
		 *        }
		 *      } );
		 *    } )
		 */
		"fnInitComplete": null,
	
	
		/**
		 * Called at the very start of each table draw and can be used to cancel the
		 * draw by returning false, any other return (including undefined) results in
		 * the full draw occurring).
		 *  @type function
		 *  @param {object} settings DataTables settings object
		 *  @returns {boolean} False will cancel the draw, anything else (including no
		 *    return) will allow it to complete.
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.preDrawCallback
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "preDrawCallback": function( settings ) {
		 *          if ( $('#test').val() == 1 ) {
		 *            return false;
		 *          }
		 *        }
		 *      } );
		 *    } );
		 */
		"fnPreDrawCallback": null,
	
	
		/**
		 * This function allows you to 'post process' each row after it have been
		 * generated for each table draw, but before it is rendered on screen. This
		 * function might be used for setting the row class name etc.
		 *  @type function
		 *  @param {node} row "TR" element for the current row
		 *  @param {array} data Raw data array for this row
		 *  @param {int} displayIndex The display index for the current table draw
		 *  @param {int} displayIndexFull The index of the data in the full list of
		 *    rows (after filtering)
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.rowCallback
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "rowCallback": function( row, data, displayIndex, displayIndexFull ) {
		 *          // Bold the grade for all 'A' grade browsers
		 *          if ( data[4] == "A" ) {
		 *            $('td:eq(4)', row).html( '<b>A</b>' );
		 *          }
		 *        }
		 *      } );
		 *    } );
		 */
		"fnRowCallback": null,
	
	
		/**
		 * __Deprecated__ The functionality provided by this parameter has now been
		 * superseded by that provided through `ajax`, which should be used instead.
		 *
		 * This parameter allows you to override the default function which obtains
		 * the data from the server so something more suitable for your application.
		 * For example you could use POST data, or pull information from a Gears or
		 * AIR database.
		 *  @type function
		 *  @member
		 *  @param {string} source HTTP source to obtain the data from (`ajax`)
		 *  @param {array} data A key/value pair object containing the data to send
		 *    to the server
		 *  @param {function} callback to be called on completion of the data get
		 *    process that will draw the data on the page.
		 *  @param {object} settings DataTables settings object
		 *
		 *  @dtopt Callbacks
		 *  @dtopt Server-side
		 *  @name DataTable.defaults.serverData
		 *
		 *  @deprecated 1.10. Please use `ajax` for this functionality now.
		 */
		"fnServerData": null,
	
	
		/**
		 * __Deprecated__ The functionality provided by this parameter has now been
		 * superseded by that provided through `ajax`, which should be used instead.
		 *
		 *  It is often useful to send extra data to the server when making an Ajax
		 * request - for example custom filtering information, and this callback
		 * function makes it trivial to send extra information to the server. The
		 * passed in parameter is the data set that has been constructed by
		 * DataTables, and you can add to this or modify it as you require.
		 *  @type function
		 *  @param {array} data Data array (array of objects which are name/value
		 *    pairs) that has been constructed by DataTables and will be sent to the
		 *    server. In the case of Ajax sourced data with server-side processing
		 *    this will be an empty array, for server-side processing there will be a
		 *    significant number of parameters!
		 *  @returns {undefined} Ensure that you modify the data array passed in,
		 *    as this is passed by reference.
		 *
		 *  @dtopt Callbacks
		 *  @dtopt Server-side
		 *  @name DataTable.defaults.serverParams
		 *
		 *  @deprecated 1.10. Please use `ajax` for this functionality now.
		 */
		"fnServerParams": null,
	
	
		/**
		 * Load the table state. With this function you can define from where, and how, the
		 * state of a table is loaded. By default DataTables will load from `localStorage`
		 * but you might wish to use a server-side database or cookies.
		 *  @type function
		 *  @member
		 *  @param {object} settings DataTables settings object
		 *  @return {object} The DataTables state object to be loaded
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.stateLoadCallback
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "stateSave": true,
		 *        "stateLoadCallback": function (settings) {
		 *          var o;
		 *
		 *          // Send an Ajax request to the server to get the data. Note that
		 *          // this is a synchronous request.
		 *          $.ajax( {
		 *            "url": "/state_load",
		 *            "async": false,
		 *            "dataType": "json",
		 *            "success": function (json) {
		 *              o = json;
		 *            }
		 *          } );
		 *
		 *          return o;
		 *        }
		 *      } );
		 *    } );
		 */
		"fnStateLoadCallback": function ( settings ) {
			try {
				return JSON.parse(
					(settings.iStateDuration === -1 ? sessionStorage : localStorage).getItem(
						'DataTables_'+settings.sInstance+'_'+location.pathname
					)
				);
			} catch (e) {}
		},
	
	
		/**
		 * Callback which allows modification of the saved state prior to loading that state.
		 * This callback is called when the table is loading state from the stored data, but
		 * prior to the settings object being modified by the saved state. Note that for
		 * plug-in authors, you should use the `stateLoadParams` event to load parameters for
		 * a plug-in.
		 *  @type function
		 *  @param {object} settings DataTables settings object
		 *  @param {object} data The state object that is to be loaded
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.stateLoadParams
		 *
		 *  @example
		 *    // Remove a saved filter, so filtering is never loaded
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "stateSave": true,
		 *        "stateLoadParams": function (settings, data) {
		 *          data.oSearch.sSearch = "";
		 *        }
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Disallow state loading by returning false
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "stateSave": true,
		 *        "stateLoadParams": function (settings, data) {
		 *          return false;
		 *        }
		 *      } );
		 *    } );
		 */
		"fnStateLoadParams": null,
	
	
		/**
		 * Callback that is called when the state has been loaded from the state saving method
		 * and the DataTables settings object has been modified as a result of the loaded state.
		 *  @type function
		 *  @param {object} settings DataTables settings object
		 *  @param {object} data The state object that was loaded
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.stateLoaded
		 *
		 *  @example
		 *    // Show an alert with the filtering value that was saved
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "stateSave": true,
		 *        "stateLoaded": function (settings, data) {
		 *          alert( 'Saved filter was: '+data.oSearch.sSearch );
		 *        }
		 *      } );
		 *    } );
		 */
		"fnStateLoaded": null,
	
	
		/**
		 * Save the table state. This function allows you to define where and how the state
		 * information for the table is stored By default DataTables will use `localStorage`
		 * but you might wish to use a server-side database or cookies.
		 *  @type function
		 *  @member
		 *  @param {object} settings DataTables settings object
		 *  @param {object} data The state object to be saved
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.stateSaveCallback
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "stateSave": true,
		 *        "stateSaveCallback": function (settings, data) {
		 *          // Send an Ajax request to the server with the state object
		 *          $.ajax( {
		 *            "url": "/state_save",
		 *            "data": data,
		 *            "dataType": "json",
		 *            "method": "POST"
		 *            "success": function () {}
		 *          } );
		 *        }
		 *      } );
		 *    } );
		 */
		"fnStateSaveCallback": function ( settings, data ) {
			try {
				(settings.iStateDuration === -1 ? sessionStorage : localStorage).setItem(
					'DataTables_'+settings.sInstance+'_'+location.pathname,
					JSON.stringify( data )
				);
			} catch (e) {}
		},
	
	
		/**
		 * Callback which allows modification of the state to be saved. Called when the table
		 * has changed state a new state save is required. This method allows modification of
		 * the state saving object prior to actually doing the save, including addition or
		 * other state properties or modification. Note that for plug-in authors, you should
		 * use the `stateSaveParams` event to save parameters for a plug-in.
		 *  @type function
		 *  @param {object} settings DataTables settings object
		 *  @param {object} data The state object to be saved
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.stateSaveParams
		 *
		 *  @example
		 *    // Remove a saved filter, so filtering is never saved
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "stateSave": true,
		 *        "stateSaveParams": function (settings, data) {
		 *          data.oSearch.sSearch = "";
		 *        }
		 *      } );
		 *    } );
		 */
		"fnStateSaveParams": null,
	
	
		/**
		 * Duration for which the saved state information is considered valid. After this period
		 * has elapsed the state will be returned to the default.
		 * Value is given in seconds.
		 *  @type int
		 *  @default 7200 <i>(2 hours)</i>
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.stateDuration
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "stateDuration": 60*60*24; // 1 day
		 *      } );
		 *    } )
		 */
		"iStateDuration": 7200,
	
	
		/**
		 * When enabled DataTables will not make a request to the server for the first
		 * page draw - rather it will use the data already on the page (no sorting etc
		 * will be applied to it), thus saving on an XHR at load time. `deferLoading`
		 * is used to indicate that deferred loading is required, but it is also used
		 * to tell DataTables how many records there are in the full table (allowing
		 * the information element and pagination to be displayed correctly). In the case
		 * where a filtering is applied to the table on initial load, this can be
		 * indicated by giving the parameter as an array, where the first element is
		 * the number of records available after filtering and the second element is the
		 * number of records without filtering (allowing the table information element
		 * to be shown correctly).
		 *  @type int | array
		 *  @default null
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.deferLoading
		 *
		 *  @example
		 *    // 57 records available in the table, no filtering applied
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "serverSide": true,
		 *        "ajax": "scripts/server_processing.php",
		 *        "deferLoading": 57
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // 57 records after filtering, 100 without filtering (an initial filter applied)
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "serverSide": true,
		 *        "ajax": "scripts/server_processing.php",
		 *        "deferLoading": [ 57, 100 ],
		 *        "search": {
		 *          "search": "my_filter"
		 *        }
		 *      } );
		 *    } );
		 */
		"iDeferLoading": null,
	
	
		/**
		 * Number of rows to display on a single page when using pagination. If
		 * feature enabled (`lengthChange`) then the end user will be able to override
		 * this to a custom setting using a pop-up menu.
		 *  @type int
		 *  @default 10
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.pageLength
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "pageLength": 50
		 *      } );
		 *    } )
		 */
		"iDisplayLength": 10,
	
	
		/**
		 * Define the starting point for data display when using DataTables with
		 * pagination. Note that this parameter is the number of records, rather than
		 * the page number, so if you have 10 records per page and want to start on
		 * the third page, it should be "20".
		 *  @type int
		 *  @default 0
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.displayStart
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "displayStart": 20
		 *      } );
		 *    } )
		 */
		"iDisplayStart": 0,
	
	
		/**
		 * By default DataTables allows keyboard navigation of the table (sorting, paging,
		 * and filtering) by adding a `tabindex` attribute to the required elements. This
		 * allows you to tab through the controls and press the enter key to activate them.
		 * The tabindex is default 0, meaning that the tab follows the flow of the document.
		 * You can overrule this using this parameter if you wish. Use a value of -1 to
		 * disable built-in keyboard navigation.
		 *  @type int
		 *  @default 0
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.tabIndex
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "tabIndex": 1
		 *      } );
		 *    } );
		 */
		"iTabIndex": 0,
	
	
		/**
		 * Classes that DataTables assigns to the various components and features
		 * that it adds to the HTML table. This allows classes to be configured
		 * during initialisation in addition to through the static
		 * {@link DataTable.ext.oStdClasses} object).
		 *  @namespace
		 *  @name DataTable.defaults.classes
		 */
		"oClasses": {},
	
	
		/**
		 * All strings that DataTables uses in the user interface that it creates
		 * are defined in this object, allowing you to modified them individually or
		 * completely replace them all as required.
		 *  @namespace
		 *  @name DataTable.defaults.language
		 */
		"oLanguage": {
			/**
			 * Strings that are used for WAI-ARIA labels and controls only (these are not
			 * actually visible on the page, but will be read by screenreaders, and thus
			 * must be internationalised as well).
			 *  @namespace
			 *  @name DataTable.defaults.language.aria
			 */
			"oAria": {
				/**
				 * ARIA label that is added to the table headers when the column may be
				 * sorted ascending by activing the column (click or return when focused).
				 * Note that the column header is prefixed to this string.
				 *  @type string
				 *  @default : activate to sort column ascending
				 *
				 *  @dtopt Language
				 *  @name DataTable.defaults.language.aria.sortAscending
				 *
				 *  @example
				 *    $(document).ready( function() {
				 *      $('#example').dataTable( {
				 *        "language": {
				 *          "aria": {
				 *            "sortAscending": " - click/return to sort ascending"
				 *          }
				 *        }
				 *      } );
				 *    } );
				 */
				"sSortAscending": ": activate to sort column ascending",
	
				/**
				 * ARIA label that is added to the table headers when the column may be
				 * sorted descending by activing the column (click or return when focused).
				 * Note that the column header is prefixed to this string.
				 *  @type string
				 *  @default : activate to sort column ascending
				 *
				 *  @dtopt Language
				 *  @name DataTable.defaults.language.aria.sortDescending
				 *
				 *  @example
				 *    $(document).ready( function() {
				 *      $('#example').dataTable( {
				 *        "language": {
				 *          "aria": {
				 *            "sortDescending": " - click/return to sort descending"
				 *          }
				 *        }
				 *      } );
				 *    } );
				 */
				"sSortDescending": ": activate to sort column descending"
			},
	
			/**
			 * Pagination string used by DataTables for the built-in pagination
			 * control types.
			 *  @namespace
			 *  @name DataTable.defaults.language.paginate
			 */
			"oPaginate": {
				/**
				 * Text to use when using the 'full_numbers' type of pagination for the
				 * button to take the user to the first page.
				 *  @type string
				 *  @default First
				 *
				 *  @dtopt Language
				 *  @name DataTable.defaults.language.paginate.first
				 *
				 *  @example
				 *    $(document).ready( function() {
				 *      $('#example').dataTable( {
				 *        "language": {
				 *          "paginate": {
				 *            "first": "First page"
				 *          }
				 *        }
				 *      } );
				 *    } );
				 */
				"sFirst": "First",
	
	
				/**
				 * Text to use when using the 'full_numbers' type of pagination for the
				 * button to take the user to the last page.
				 *  @type string
				 *  @default Last
				 *
				 *  @dtopt Language
				 *  @name DataTable.defaults.language.paginate.last
				 *
				 *  @example
				 *    $(document).ready( function() {
				 *      $('#example').dataTable( {
				 *        "language": {
				 *          "paginate": {
				 *            "last": "Last page"
				 *          }
				 *        }
				 *      } );
				 *    } );
				 */
				"sLast": "Last",
	
	
				/**
				 * Text to use for the 'next' pagination button (to take the user to the
				 * next page).
				 *  @type string
				 *  @default Next
				 *
				 *  @dtopt Language
				 *  @name DataTable.defaults.language.paginate.next
				 *
				 *  @example
				 *    $(document).ready( function() {
				 *      $('#example').dataTable( {
				 *        "language": {
				 *          "paginate": {
				 *            "next": "Next page"
				 *          }
				 *        }
				 *      } );
				 *    } );
				 */
				"sNext": "Next",
	
	
				/**
				 * Text to use for the 'previous' pagination button (to take the user to
				 * the previous page).
				 *  @type string
				 *  @default Previous
				 *
				 *  @dtopt Language
				 *  @name DataTable.defaults.language.paginate.previous
				 *
				 *  @example
				 *    $(document).ready( function() {
				 *      $('#example').dataTable( {
				 *        "language": {
				 *          "paginate": {
				 *            "previous": "Previous page"
				 *          }
				 *        }
				 *      } );
				 *    } );
				 */
				"sPrevious": "Previous"
			},
	
			/**
			 * This string is shown in preference to `zeroRecords` when the table is
			 * empty of data (regardless of filtering). Note that this is an optional
			 * parameter - if it is not given, the value of `zeroRecords` will be used
			 * instead (either the default or given value).
			 *  @type string
			 *  @default No data available in table
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.emptyTable
			 *
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "emptyTable": "No data available in table"
			 *        }
			 *      } );
			 *    } );
			 */
			"sEmptyTable": "No data available in table",
	
	
			/**
			 * This string gives information to the end user about the information
			 * that is current on display on the page. The following tokens can be
			 * used in the string and will be dynamically replaced as the table
			 * display updates. This tokens can be placed anywhere in the string, or
			 * removed as needed by the language requires:
			 *
			 * * `\_START\_` - Display index of the first record on the current page
			 * * `\_END\_` - Display index of the last record on the current page
			 * * `\_TOTAL\_` - Number of records in the table after filtering
			 * * `\_MAX\_` - Number of records in the table without filtering
			 * * `\_PAGE\_` - Current page number
			 * * `\_PAGES\_` - Total number of pages of data in the table
			 *
			 *  @type string
			 *  @default Showing _START_ to _END_ of _TOTAL_ entries
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.info
			 *
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "info": "Showing page _PAGE_ of _PAGES_"
			 *        }
			 *      } );
			 *    } );
			 */
			"sInfo": "Showing _START_ to _END_ of _TOTAL_ entries",
	
	
			/**
			 * Display information string for when the table is empty. Typically the
			 * format of this string should match `info`.
			 *  @type string
			 *  @default Showing 0 to 0 of 0 entries
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.infoEmpty
			 *
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "infoEmpty": "No entries to show"
			 *        }
			 *      } );
			 *    } );
			 */
			"sInfoEmpty": "Showing 0 to 0 of 0 entries",
	
	
			/**
			 * When a user filters the information in a table, this string is appended
			 * to the information (`info`) to give an idea of how strong the filtering
			 * is. The variable _MAX_ is dynamically updated.
			 *  @type string
			 *  @default (filtered from _MAX_ total entries)
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.infoFiltered
			 *
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "infoFiltered": " - filtering from _MAX_ records"
			 *        }
			 *      } );
			 *    } );
			 */
			"sInfoFiltered": "(filtered from _MAX_ total entries)",
	
	
			/**
			 * If can be useful to append extra information to the info string at times,
			 * and this variable does exactly that. This information will be appended to
			 * the `info` (`infoEmpty` and `infoFiltered` in whatever combination they are
			 * being used) at all times.
			 *  @type string
			 *  @default <i>Empty string</i>
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.infoPostFix
			 *
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "infoPostFix": "All records shown are derived from real information."
			 *        }
			 *      } );
			 *    } );
			 */
			"sInfoPostFix": "",
	
	
			/**
			 * This decimal place operator is a little different from the other
			 * language options since DataTables doesn't output floating point
			 * numbers, so it won't ever use this for display of a number. Rather,
			 * what this parameter does is modify the sort methods of the table so
			 * that numbers which are in a format which has a character other than
			 * a period (`.`) as a decimal place will be sorted numerically.
			 *
			 * Note that numbers with different decimal places cannot be shown in
			 * the same table and still be sortable, the table must be consistent.
			 * However, multiple different tables on the page can use different
			 * decimal place characters.
			 *  @type string
			 *  @default 
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.decimal
			 *
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "decimal": ","
			 *          "thousands": "."
			 *        }
			 *      } );
			 *    } );
			 */
			"sDecimal": "",
	
	
			/**
			 * DataTables has a build in number formatter (`formatNumber`) which is
			 * used to format large numbers that are used in the table information.
			 * By default a comma is used, but this can be trivially changed to any
			 * character you wish with this parameter.
			 *  @type string
			 *  @default ,
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.thousands
			 *
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "thousands": "'"
			 *        }
			 *      } );
			 *    } );
			 */
			"sThousands": ",",
	
	
			/**
			 * Detail the action that will be taken when the drop down menu for the
			 * pagination length option is changed. The '_MENU_' variable is replaced
			 * with a default select list of 10, 25, 50 and 100, and can be replaced
			 * with a custom select box if required.
			 *  @type string
			 *  @default Show _MENU_ entries
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.lengthMenu
			 *
			 *  @example
			 *    // Language change only
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "lengthMenu": "Display _MENU_ records"
			 *        }
			 *      } );
			 *    } );
			 *
			 *  @example
			 *    // Language and options change
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "lengthMenu": 'Display <select>'+
			 *            '<option value="10">10</option>'+
			 *            '<option value="20">20</option>'+
			 *            '<option value="30">30</option>'+
			 *            '<option value="40">40</option>'+
			 *            '<option value="50">50</option>'+
			 *            '<option value="-1">All</option>'+
			 *            '</select> records'
			 *        }
			 *      } );
			 *    } );
			 */
			"sLengthMenu": "Show _MENU_ entries",
	
	
			/**
			 * When using Ajax sourced data and during the first draw when DataTables is
			 * gathering the data, this message is shown in an empty row in the table to
			 * indicate to the end user the the data is being loaded. Note that this
			 * parameter is not used when loading data by server-side processing, just
			 * Ajax sourced data with client-side processing.
			 *  @type string
			 *  @default Loading...
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.loadingRecords
			 *
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "loadingRecords": "Please wait - loading..."
			 *        }
			 *      } );
			 *    } );
			 */
			"sLoadingRecords": "Loading...",
	
	
			/**
			 * Text which is displayed when the table is processing a user action
			 * (usually a sort command or similar).
			 *  @type string
			 *  @default Processing...
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.processing
			 *
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "processing": "DataTables is currently busy"
			 *        }
			 *      } );
			 *    } );
			 */
			"sProcessing": "Processing...",
	
	
			/**
			 * Details the actions that will be taken when the user types into the
			 * filtering input text box. The variable "_INPUT_", if used in the string,
			 * is replaced with the HTML text box for the filtering input allowing
			 * control over where it appears in the string. If "_INPUT_" is not given
			 * then the input box is appended to the string automatically.
			 *  @type string
			 *  @default Search:
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.search
			 *
			 *  @example
			 *    // Input text box will be appended at the end automatically
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "search": "Filter records:"
			 *        }
			 *      } );
			 *    } );
			 *
			 *  @example
			 *    // Specify where the filter should appear
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "search": "Apply filter _INPUT_ to table"
			 *        }
			 *      } );
			 *    } );
			 */
			"sSearch": "Search:",
	
	
			/**
			 * Assign a `placeholder` attribute to the search `input` element
			 *  @type string
			 *  @default 
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.searchPlaceholder
			 */
			"sSearchPlaceholder": "",
	
	
			/**
			 * All of the language information can be stored in a file on the
			 * server-side, which DataTables will look up if this parameter is passed.
			 * It must store the URL of the language file, which is in a JSON format,
			 * and the object has the same properties as the oLanguage object in the
			 * initialiser object (i.e. the above parameters). Please refer to one of
			 * the example language files to see how this works in action.
			 *  @type string
			 *  @default <i>Empty string - i.e. disabled</i>
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.url
			 *
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "url": "http://www.sprymedia.co.uk/dataTables/lang.txt"
			 *        }
			 *      } );
			 *    } );
			 */
			"sUrl": "",
	
	
			/**
			 * Text shown inside the table records when the is no information to be
			 * displayed after filtering. `emptyTable` is shown when there is simply no
			 * information in the table at all (regardless of filtering).
			 *  @type string
			 *  @default No matching records found
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.zeroRecords
			 *
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "zeroRecords": "No records to display"
			 *        }
			 *      } );
			 *    } );
			 */
			"sZeroRecords": "No matching records found"
		},
	
	
		/**
		 * This parameter allows you to have define the global filtering state at
		 * initialisation time. As an object the `search` parameter must be
		 * defined, but all other parameters are optional. When `regex` is true,
		 * the search string will be treated as a regular expression, when false
		 * (default) it will be treated as a straight string. When `smart`
		 * DataTables will use it's smart filtering methods (to word match at
		 * any point in the data), when false this will not be done.
		 *  @namespace
		 *  @extends DataTable.models.oSearch
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.search
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "search": {"search": "Initial search"}
		 *      } );
		 *    } )
		 */
		"oSearch": $.extend( {}, DataTable.models.oSearch ),
	
	
		/**
		 * __Deprecated__ The functionality provided by this parameter has now been
		 * superseded by that provided through `ajax`, which should be used instead.
		 *
		 * By default DataTables will look for the property `data` (or `aaData` for
		 * compatibility with DataTables 1.9-) when obtaining data from an Ajax
		 * source or for server-side processing - this parameter allows that
		 * property to be changed. You can use Javascript dotted object notation to
		 * get a data source for multiple levels of nesting.
		 *  @type string
		 *  @default data
		 *
		 *  @dtopt Options
		 *  @dtopt Server-side
		 *  @name DataTable.defaults.ajaxDataProp
		 *
		 *  @deprecated 1.10. Please use `ajax` for this functionality now.
		 */
		"sAjaxDataProp": "data",
	
	
		/**
		 * __Deprecated__ The functionality provided by this parameter has now been
		 * superseded by that provided through `ajax`, which should be used instead.
		 *
		 * You can instruct DataTables to load data from an external
		 * source using this parameter (use aData if you want to pass data in you
		 * already have). Simply provide a url a JSON object can be obtained from.
		 *  @type string
		 *  @default null
		 *
		 *  @dtopt Options
		 *  @dtopt Server-side
		 *  @name DataTable.defaults.ajaxSource
		 *
		 *  @deprecated 1.10. Please use `ajax` for this functionality now.
		 */
		"sAjaxSource": null,
	
	
		/**
		 * This initialisation variable allows you to specify exactly where in the
		 * DOM you want DataTables to inject the various controls it adds to the page
		 * (for example you might want the pagination controls at the top of the
		 * table). DIV elements (with or without a custom class) can also be added to
		 * aid styling. The follow syntax is used:
		 *   <ul>
		 *     <li>The following options are allowed:
		 *       <ul>
		 *         <li>'l' - Length changing</li>
		 *         <li>'f' - Filtering input</li>
		 *         <li>'t' - The table!</li>
		 *         <li>'i' - Information</li>
		 *         <li>'p' - Pagination</li>
		 *         <li>'r' - pRocessing</li>
		 *       </ul>
		 *     </li>
		 *     <li>The following constants are allowed:
		 *       <ul>
		 *         <li>'H' - jQueryUI theme "header" classes ('fg-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix')</li>
		 *         <li>'F' - jQueryUI theme "footer" classes ('fg-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix')</li>
		 *       </ul>
		 *     </li>
		 *     <li>The following syntax is expected:
		 *       <ul>
		 *         <li>'&lt;' and '&gt;' - div elements</li>
		 *         <li>'&lt;"class" and '&gt;' - div with a class</li>
		 *         <li>'&lt;"#id" and '&gt;' - div with an ID</li>
		 *       </ul>
		 *     </li>
		 *     <li>Examples:
		 *       <ul>
		 *         <li>'&lt;"wrapper"flipt&gt;'</li>
		 *         <li>'&lt;lf&lt;t&gt;ip&gt;'</li>
		 *       </ul>
		 *     </li>
		 *   </ul>
		 *  @type string
		 *  @default lfrtip <i>(when `jQueryUI` is false)</i> <b>or</b>
		 *    <"H"lfr>t<"F"ip> <i>(when `jQueryUI` is true)</i>
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.dom
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "dom": '&lt;"top"i&gt;rt&lt;"bottom"flp&gt;&lt;"clear"&gt;'
		 *      } );
		 *    } );
		 */
		"sDom": "lfrtip",
	
	
		/**
		 * Search delay option. This will throttle full table searches that use the
		 * DataTables provided search input element (it does not effect calls to
		 * `dt-api search()`, providing a delay before the search is made.
		 *  @type integer
		 *  @default 0
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.searchDelay
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "searchDelay": 200
		 *      } );
		 *    } )
		 */
		"searchDelay": null,
	
	
		/**
		 * DataTables features four different built-in options for the buttons to
		 * display for pagination control:
		 *
		 * * `simple` - 'Previous' and 'Next' buttons only
		 * * 'simple_numbers` - 'Previous' and 'Next' buttons, plus page numbers
		 * * `full` - 'First', 'Previous', 'Next' and 'Last' buttons
		 * * `full_numbers` - 'First', 'Previous', 'Next' and 'Last' buttons, plus
		 *   page numbers
		 *  
		 * Further methods can be added using {@link DataTable.ext.oPagination}.
		 *  @type string
		 *  @default simple_numbers
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.pagingType
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "pagingType": "full_numbers"
		 *      } );
		 *    } )
		 */
		"sPaginationType": "simple_numbers",
	
	
		/**
		 * Enable horizontal scrolling. When a table is too wide to fit into a
		 * certain layout, or you have a large number of columns in the table, you
		 * can enable x-scrolling to show the table in a viewport, which can be
		 * scrolled. This property can be `true` which will allow the table to
		 * scroll horizontally when needed, or any CSS unit, or a number (in which
		 * case it will be treated as a pixel measurement). Setting as simply `true`
		 * is recommended.
		 *  @type boolean|string
		 *  @default <i>blank string - i.e. disabled</i>
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.scrollX
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "scrollX": true,
		 *        "scrollCollapse": true
		 *      } );
		 *    } );
		 */
		"sScrollX": "",
	
	
		/**
		 * This property can be used to force a DataTable to use more width than it
		 * might otherwise do when x-scrolling is enabled. For example if you have a
		 * table which requires to be well spaced, this parameter is useful for
		 * "over-sizing" the table, and thus forcing scrolling. This property can by
		 * any CSS unit, or a number (in which case it will be treated as a pixel
		 * measurement).
		 *  @type string
		 *  @default <i>blank string - i.e. disabled</i>
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.scrollXInner
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "scrollX": "100%",
		 *        "scrollXInner": "110%"
		 *      } );
		 *    } );
		 */
		"sScrollXInner": "",
	
	
		/**
		 * Enable vertical scrolling. Vertical scrolling will constrain the DataTable
		 * to the given height, and enable scrolling for any data which overflows the
		 * current viewport. This can be used as an alternative to paging to display
		 * a lot of data in a small area (although paging and scrolling can both be
		 * enabled at the same time). This property can be any CSS unit, or a number
		 * (in which case it will be treated as a pixel measurement).
		 *  @type string
		 *  @default <i>blank string - i.e. disabled</i>
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.scrollY
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "scrollY": "200px",
		 *        "paginate": false
		 *      } );
		 *    } );
		 */
		"sScrollY": "",
	
	
		/**
		 * __Deprecated__ The functionality provided by this parameter has now been
		 * superseded by that provided through `ajax`, which should be used instead.
		 *
		 * Set the HTTP method that is used to make the Ajax call for server-side
		 * processing or Ajax sourced data.
		 *  @type string
		 *  @default GET
		 *
		 *  @dtopt Options
		 *  @dtopt Server-side
		 *  @name DataTable.defaults.serverMethod
		 *
		 *  @deprecated 1.10. Please use `ajax` for this functionality now.
		 */
		"sServerMethod": "GET",
	
	
		/**
		 * DataTables makes use of renderers when displaying HTML elements for
		 * a table. These renderers can be added or modified by plug-ins to
		 * generate suitable mark-up for a site. For example the Bootstrap
		 * integration plug-in for DataTables uses a paging button renderer to
		 * display pagination buttons in the mark-up required by Bootstrap.
		 *
		 * For further information about the renderers available see
		 * DataTable.ext.renderer
		 *  @type string|object
		 *  @default null
		 *
		 *  @name DataTable.defaults.renderer
		 *
		 */
		"renderer": null,
	
	
		/**
		 * Set the data property name that DataTables should use to get a row's id
		 * to set as the `id` property in the node.
		 *  @type string
		 *  @default DT_RowId
		 *
		 *  @name DataTable.defaults.rowId
		 */
		"rowId": "DT_RowId"
	};
	
	_fnHungarianMap( DataTable.defaults );
	
	
	
	/*
	 * Developer note - See note in model.defaults.js about the use of Hungarian
	 * notation and camel case.
	 */
	
	/**
	 * Column options that can be given to DataTables at initialisation time.
	 *  @namespace
	 */
	DataTable.defaults.column = {
		/**
		 * Define which column(s) an order will occur on for this column. This
		 * allows a column's ordering to take multiple columns into account when
		 * doing a sort or use the data from a different column. For example first
		 * name / last name columns make sense to do a multi-column sort over the
		 * two columns.
		 *  @type array|int
		 *  @default null <i>Takes the value of the column index automatically</i>
		 *
		 *  @name DataTable.defaults.column.orderData
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          { "orderData": [ 0, 1 ], "targets": [ 0 ] },
		 *          { "orderData": [ 1, 0 ], "targets": [ 1 ] },
		 *          { "orderData": 2, "targets": [ 2 ] }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          { "orderData": [ 0, 1 ] },
		 *          { "orderData": [ 1, 0 ] },
		 *          { "orderData": 2 },
		 *          null,
		 *          null
		 *        ]
		 *      } );
		 *    } );
		 */
		"aDataSort": null,
		"iDataSort": -1,
	
	
		/**
		 * You can control the default ordering direction, and even alter the
		 * behaviour of the sort handler (i.e. only allow ascending ordering etc)
		 * using this parameter.
		 *  @type array
		 *  @default [ 'asc', 'desc' ]
		 *
		 *  @name DataTable.defaults.column.orderSequence
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          { "orderSequence": [ "asc" ], "targets": [ 1 ] },
		 *          { "orderSequence": [ "desc", "asc", "asc" ], "targets": [ 2 ] },
		 *          { "orderSequence": [ "desc" ], "targets": [ 3 ] }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          null,
		 *          { "orderSequence": [ "asc" ] },
		 *          { "orderSequence": [ "desc", "asc", "asc" ] },
		 *          { "orderSequence": [ "desc" ] },
		 *          null
		 *        ]
		 *      } );
		 *    } );
		 */
		"asSorting": [ 'asc', 'desc' ],
	
	
		/**
		 * Enable or disable filtering on the data in this column.
		 *  @type boolean
		 *  @default true
		 *
		 *  @name DataTable.defaults.column.searchable
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          { "searchable": false, "targets": [ 0 ] }
		 *        ] } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          { "searchable": false },
		 *          null,
		 *          null,
		 *          null,
		 *          null
		 *        ] } );
		 *    } );
		 */
		"bSearchable": true,
	
	
		/**
		 * Enable or disable ordering on this column.
		 *  @type boolean
		 *  @default true
		 *
		 *  @name DataTable.defaults.column.orderable
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          { "orderable": false, "targets": [ 0 ] }
		 *        ] } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          { "orderable": false },
		 *          null,
		 *          null,
		 *          null,
		 *          null
		 *        ] } );
		 *    } );
		 */
		"bSortable": true,   
	
	
		/**
		 * Enable or disable the display of this column.
		 *  @type boolean
		 *  @default true
		 *
		 *  @name DataTable.defaults.column.visible
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          { "visible": false, "targets": [ 0 ] }
		 *        ] } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          { "visible": false },
		 *          null,
		 *          null,
		 *          null,
		 *          null
		 *        ] } );
		 *    } );
		 */
		"bVisible": true,
	
	
		/**
		 * Developer definable function that is called whenever a cell is created (Ajax source,
		 * etc) or processed for input (DOM source). This can be used as a compliment to mRender
		 * allowing you to modify the DOM element (add background colour for example) when the
		 * element is available.
		 *  @type function
		 *  @param {element} td The TD node that has been created
		 *  @param {*} cellData The Data for the cell
		 *  @param {array|object} rowData The data for the whole row
		 *  @param {int} row The row index for the aoData data store
		 *  @param {int} col The column index for aoColumns
		 *
		 *  @name DataTable.defaults.column.createdCell
		 *  @dtopt Columns
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [ {
		 *          "targets": [3],
		 *          "createdCell": function (td, cellData, rowData, row, col) {
		 *            if ( cellData == "1.7" ) {
		 *              $(td).css('color', 'blue')
		 *            }
		 *          }
		 *        } ]
		 *      });
		 *    } );
		 */
		"fnCreatedCell": null,
	
	
		/**
		 * This parameter has been replaced by `data` in DataTables to ensure naming
		 * consistency. `dataProp` can still be used, as there is backwards
		 * compatibility in DataTables for this option, but it is strongly
		 * recommended that you use `data` in preference to `dataProp`.
		 *  @name DataTable.defaults.column.dataProp
		 */
	
	
		/**
		 * This property can be used to read data from any data source property,
		 * including deeply nested objects / properties. `data` can be given in a
		 * number of different ways which effect its behaviour:
		 *
		 * * `integer` - treated as an array index for the data source. This is the
		 *   default that DataTables uses (incrementally increased for each column).
		 * * `string` - read an object property from the data source. There are
		 *   three 'special' options that can be used in the string to alter how
		 *   DataTables reads the data from the source object:
		 *    * `.` - Dotted Javascript notation. Just as you use a `.` in
		 *      Javascript to read from nested objects, so to can the options
		 *      specified in `data`. For example: `browser.version` or
		 *      `browser.name`. If your object parameter name contains a period, use
		 *      `\\` to escape it - i.e. `first\\.name`.
		 *    * `[]` - Array notation. DataTables can automatically combine data
		 *      from and array source, joining the data with the characters provided
		 *      between the two brackets. For example: `name[, ]` would provide a
		 *      comma-space separated list from the source array. If no characters
		 *      are provided between the brackets, the original array source is
		 *      returned.
		 *    * `()` - Function notation. Adding `()` to the end of a parameter will
		 *      execute a function of the name given. For example: `browser()` for a
		 *      simple function on the data source, `browser.version()` for a
		 *      function in a nested property or even `browser().version` to get an
		 *      object property if the function called returns an object. Note that
		 *      function notation is recommended for use in `render` rather than
		 *      `data` as it is much simpler to use as a renderer.
		 * * `null` - use the original data source for the row rather than plucking
		 *   data directly from it. This action has effects on two other
		 *   initialisation options:
		 *    * `defaultContent` - When null is given as the `data` option and
		 *      `defaultContent` is specified for the column, the value defined by
		 *      `defaultContent` will be used for the cell.
		 *    * `render` - When null is used for the `data` option and the `render`
		 *      option is specified for the column, the whole data source for the
		 *      row is used for the renderer.
		 * * `function` - the function given will be executed whenever DataTables
		 *   needs to set or get the data for a cell in the column. The function
		 *   takes three parameters:
		 *    * Parameters:
		 *      * `{array|object}` The data source for the row
		 *      * `{string}` The type call data requested - this will be 'set' when
		 *        setting data or 'filter', 'display', 'type', 'sort' or undefined
		 *        when gathering data. Note that when `undefined` is given for the
		 *        type DataTables expects to get the raw data for the object back<
		 *      * `{*}` Data to set when the second parameter is 'set'.
		 *    * Return:
		 *      * The return value from the function is not required when 'set' is
		 *        the type of call, but otherwise the return is what will be used
		 *        for the data requested.
		 *
		 * Note that `data` is a getter and setter option. If you just require
		 * formatting of data for output, you will likely want to use `render` which
		 * is simply a getter and thus simpler to use.
		 *
		 * Note that prior to DataTables 1.9.2 `data` was called `mDataProp`. The
		 * name change reflects the flexibility of this property and is consistent
		 * with the naming of mRender. If 'mDataProp' is given, then it will still
		 * be used by DataTables, as it automatically maps the old name to the new
		 * if required.
		 *
		 *  @type string|int|function|null
		 *  @default null <i>Use automatically calculated column index</i>
		 *
		 *  @name DataTable.defaults.column.data
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Read table data from objects
		 *    // JSON structure for each row:
		 *    //   {
		 *    //      "engine": {value},
		 *    //      "browser": {value},
		 *    //      "platform": {value},
		 *    //      "version": {value},
		 *    //      "grade": {value}
		 *    //   }
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "ajaxSource": "sources/objects.txt",
		 *        "columns": [
		 *          { "data": "engine" },
		 *          { "data": "browser" },
		 *          { "data": "platform" },
		 *          { "data": "version" },
		 *          { "data": "grade" }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Read information from deeply nested objects
		 *    // JSON structure for each row:
		 *    //   {
		 *    //      "engine": {value},
		 *    //      "browser": {value},
		 *    //      "platform": {
		 *    //         "inner": {value}
		 *    //      },
		 *    //      "details": [
		 *    //         {value}, {value}
		 *    //      ]
		 *    //   }
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "ajaxSource": "sources/deep.txt",
		 *        "columns": [
		 *          { "data": "engine" },
		 *          { "data": "browser" },
		 *          { "data": "platform.inner" },
		 *          { "data": "platform.details.0" },
		 *          { "data": "platform.details.1" }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using `data` as a function to provide different information for
		 *    // sorting, filtering and display. In this case, currency (price)
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [ {
		 *          "targets": [ 0 ],
		 *          "data": function ( source, type, val ) {
		 *            if (type === 'set') {
		 *              source.price = val;
		 *              // Store the computed dislay and filter values for efficiency
		 *              source.price_display = val=="" ? "" : "$"+numberFormat(val);
		 *              source.price_filter  = val=="" ? "" : "$"+numberFormat(val)+" "+val;
		 *              return;
		 *            }
		 *            else if (type === 'display') {
		 *              return source.price_display;
		 *            }
		 *            else if (type === 'filter') {
		 *              return source.price_filter;
		 *            }
		 *            // 'sort', 'type' and undefined all just use the integer
		 *            return source.price;
		 *          }
		 *        } ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using default content
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [ {
		 *          "targets": [ 0 ],
		 *          "data": null,
		 *          "defaultContent": "Click to edit"
		 *        } ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using array notation - outputting a list from an array
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [ {
		 *          "targets": [ 0 ],
		 *          "data": "name[, ]"
		 *        } ]
		 *      } );
		 *    } );
		 *
		 */
		"mData": null,
	
	
		/**
		 * This property is the rendering partner to `data` and it is suggested that
		 * when you want to manipulate data for display (including filtering,
		 * sorting etc) without altering the underlying data for the table, use this
		 * property. `render` can be considered to be the the read only companion to
		 * `data` which is read / write (then as such more complex). Like `data`
		 * this option can be given in a number of different ways to effect its
		 * behaviour:
		 *
		 * * `integer` - treated as an array index for the data source. This is the
		 *   default that DataTables uses (incrementally increased for each column).
		 * * `string` - read an object property from the data source. There are
		 *   three 'special' options that can be used in the string to alter how
		 *   DataTables reads the data from the source object:
		 *    * `.` - Dotted Javascript notation. Just as you use a `.` in
		 *      Javascript to read from nested objects, so to can the options
		 *      specified in `data`. For example: `browser.version` or
		 *      `browser.name`. If your object parameter name contains a period, use
		 *      `\\` to escape it - i.e. `first\\.name`.
		 *    * `[]` - Array notation. DataTables can automatically combine data
		 *      from and array source, joining the data with the characters provided
		 *      between the two brackets. For example: `name[, ]` would provide a
		 *      comma-space separated list from the source array. If no characters
		 *      are provided between the brackets, the original array source is
		 *      returned.
		 *    * `()` - Function notation. Adding `()` to the end of a parameter will
		 *      execute a function of the name given. For example: `browser()` for a
		 *      simple function on the data source, `browser.version()` for a
		 *      function in a nested property or even `browser().version` to get an
		 *      object property if the function called returns an object.
		 * * `object` - use different data for the different data types requested by
		 *   DataTables ('filter', 'display', 'type' or 'sort'). The property names
		 *   of the object is the data type the property refers to and the value can
		 *   defined using an integer, string or function using the same rules as
		 *   `render` normally does. Note that an `_` option _must_ be specified.
		 *   This is the default value to use if you haven't specified a value for
		 *   the data type requested by DataTables.
		 * * `function` - the function given will be executed whenever DataTables
		 *   needs to set or get the data for a cell in the column. The function
		 *   takes three parameters:
		 *    * Parameters:
		 *      * {array|object} The data source for the row (based on `data`)
		 *      * {string} The type call data requested - this will be 'filter',
		 *        'display', 'type' or 'sort'.
		 *      * {array|object} The full data source for the row (not based on
		 *        `data`)
		 *    * Return:
		 *      * The return value from the function is what will be used for the
		 *        data requested.
		 *
		 *  @type string|int|function|object|null
		 *  @default null Use the data source value.
		 *
		 *  @name DataTable.defaults.column.render
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Create a comma separated list from an array of objects
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "ajaxSource": "sources/deep.txt",
		 *        "columns": [
		 *          { "data": "engine" },
		 *          { "data": "browser" },
		 *          {
		 *            "data": "platform",
		 *            "render": "[, ].name"
		 *          }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Execute a function to obtain data
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [ {
		 *          "targets": [ 0 ],
		 *          "data": null, // Use the full data source object for the renderer's source
		 *          "render": "browserName()"
		 *        } ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // As an object, extracting different data for the different types
		 *    // This would be used with a data source such as:
		 *    //   { "phone": 5552368, "phone_filter": "5552368 555-2368", "phone_display": "555-2368" }
		 *    // Here the `phone` integer is used for sorting and type detection, while `phone_filter`
		 *    // (which has both forms) is used for filtering for if a user inputs either format, while
		 *    // the formatted phone number is the one that is shown in the table.
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [ {
		 *          "targets": [ 0 ],
		 *          "data": null, // Use the full data source object for the renderer's source
		 *          "render": {
		 *            "_": "phone",
		 *            "filter": "phone_filter",
		 *            "display": "phone_display"
		 *          }
		 *        } ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Use as a function to create a link from the data source
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [ {
		 *          "targets": [ 0 ],
		 *          "data": "download_link",
		 *          "render": function ( data, type, full ) {
		 *            return '<a href="'+data+'">Download</a>';
		 *          }
		 *        } ]
		 *      } );
		 *    } );
		 */
		"mRender": null,
	
	
		/**
		 * Change the cell type created for the column - either TD cells or TH cells. This
		 * can be useful as TH cells have semantic meaning in the table body, allowing them
		 * to act as a header for a row (you may wish to add scope='row' to the TH elements).
		 *  @type string
		 *  @default td
		 *
		 *  @name DataTable.defaults.column.cellType
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Make the first column use TH cells
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [ {
		 *          "targets": [ 0 ],
		 *          "cellType": "th"
		 *        } ]
		 *      } );
		 *    } );
		 */
		"sCellType": "td",
	
	
		/**
		 * Class to give to each cell in this column.
		 *  @type string
		 *  @default <i>Empty string</i>
		 *
		 *  @name DataTable.defaults.column.class
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          { "class": "my_class", "targets": [ 0 ] }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          { "class": "my_class" },
		 *          null,
		 *          null,
		 *          null,
		 *          null
		 *        ]
		 *      } );
		 *    } );
		 */
		"sClass": "",
	
		/**
		 * When DataTables calculates the column widths to assign to each column,
		 * it finds the longest string in each column and then constructs a
		 * temporary table and reads the widths from that. The problem with this
		 * is that "mmm" is much wider then "iiii", but the latter is a longer
		 * string - thus the calculation can go wrong (doing it properly and putting
		 * it into an DOM object and measuring that is horribly(!) slow). Thus as
		 * a "work around" we provide this option. It will append its value to the
		 * text that is found to be the longest string for the column - i.e. padding.
		 * Generally you shouldn't need this!
		 *  @type string
		 *  @default <i>Empty string<i>
		 *
		 *  @name DataTable.defaults.column.contentPadding
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          null,
		 *          null,
		 *          null,
		 *          {
		 *            "contentPadding": "mmm"
		 *          }
		 *        ]
		 *      } );
		 *    } );
		 */
		"sContentPadding": "",
	
	
		/**
		 * Allows a default value to be given for a column's data, and will be used
		 * whenever a null data source is encountered (this can be because `data`
		 * is set to null, or because the data source itself is null).
		 *  @type string
		 *  @default null
		 *
		 *  @name DataTable.defaults.column.defaultContent
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          {
		 *            "data": null,
		 *            "defaultContent": "Edit",
		 *            "targets": [ -1 ]
		 *          }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          null,
		 *          null,
		 *          null,
		 *          {
		 *            "data": null,
		 *            "defaultContent": "Edit"
		 *          }
		 *        ]
		 *      } );
		 *    } );
		 */
		"sDefaultContent": null,
	
	
		/**
		 * This parameter is only used in DataTables' server-side processing. It can
		 * be exceptionally useful to know what columns are being displayed on the
		 * client side, and to map these to database fields. When defined, the names
		 * also allow DataTables to reorder information from the server if it comes
		 * back in an unexpected order (i.e. if you switch your columns around on the
		 * client-side, your server-side code does not also need updating).
		 *  @type string
		 *  @default <i>Empty string</i>
		 *
		 *  @name DataTable.defaults.column.name
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          { "name": "engine", "targets": [ 0 ] },
		 *          { "name": "browser", "targets": [ 1 ] },
		 *          { "name": "platform", "targets": [ 2 ] },
		 *          { "name": "version", "targets": [ 3 ] },
		 *          { "name": "grade", "targets": [ 4 ] }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          { "name": "engine" },
		 *          { "name": "browser" },
		 *          { "name": "platform" },
		 *          { "name": "version" },
		 *          { "name": "grade" }
		 *        ]
		 *      } );
		 *    } );
		 */
		"sName": "",
	
	
		/**
		 * Defines a data source type for the ordering which can be used to read
		 * real-time information from the table (updating the internally cached
		 * version) prior to ordering. This allows ordering to occur on user
		 * editable elements such as form inputs.
		 *  @type string
		 *  @default std
		 *
		 *  @name DataTable.defaults.column.orderDataType
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          { "orderDataType": "dom-text", "targets": [ 2, 3 ] },
		 *          { "type": "numeric", "targets": [ 3 ] },
		 *          { "orderDataType": "dom-select", "targets": [ 4 ] },
		 *          { "orderDataType": "dom-checkbox", "targets": [ 5 ] }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          null,
		 *          null,
		 *          { "orderDataType": "dom-text" },
		 *          { "orderDataType": "dom-text", "type": "numeric" },
		 *          { "orderDataType": "dom-select" },
		 *          { "orderDataType": "dom-checkbox" }
		 *        ]
		 *      } );
		 *    } );
		 */
		"sSortDataType": "std",
	
	
		/**
		 * The title of this column.
		 *  @type string
		 *  @default null <i>Derived from the 'TH' value for this column in the
		 *    original HTML table.</i>
		 *
		 *  @name DataTable.defaults.column.title
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          { "title": "My column title", "targets": [ 0 ] }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          { "title": "My column title" },
		 *          null,
		 *          null,
		 *          null,
		 *          null
		 *        ]
		 *      } );
		 *    } );
		 */
		"sTitle": null,
	
	
		/**
		 * The type allows you to specify how the data for this column will be
		 * ordered. Four types (string, numeric, date and html (which will strip
		 * HTML tags before ordering)) are currently available. Note that only date
		 * formats understood by Javascript's Date() object will be accepted as type
		 * date. For example: "Mar 26, 2008 5:03 PM". May take the values: 'string',
		 * 'numeric', 'date' or 'html' (by default). Further types can be adding
		 * through plug-ins.
		 *  @type string
		 *  @default null <i>Auto-detected from raw data</i>
		 *
		 *  @name DataTable.defaults.column.type
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          { "type": "html", "targets": [ 0 ] }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          { "type": "html" },
		 *          null,
		 *          null,
		 *          null,
		 *          null
		 *        ]
		 *      } );
		 *    } );
		 */
		"sType": null,
	
	
		/**
		 * Defining the width of the column, this parameter may take any CSS value
		 * (3em, 20px etc). DataTables applies 'smart' widths to columns which have not
		 * been given a specific width through this interface ensuring that the table
		 * remains readable.
		 *  @type string
		 *  @default null <i>Automatic</i>
		 *
		 *  @name DataTable.defaults.column.width
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          { "width": "20%", "targets": [ 0 ] }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          { "width": "20%" },
		 *          null,
		 *          null,
		 *          null,
		 *          null
		 *        ]
		 *      } );
		 *    } );
		 */
		"sWidth": null
	};
	
	_fnHungarianMap( DataTable.defaults.column );
	
	
	
	/**
	 * DataTables settings object - this holds all the information needed for a
	 * given table, including configuration, data and current application of the
	 * table options. DataTables does not have a single instance for each DataTable
	 * with the settings attached to that instance, but rather instances of the
	 * DataTable "class" are created on-the-fly as needed (typically by a
	 * $().dataTable() call) and the settings object is then applied to that
	 * instance.
	 *
	 * Note that this object is related to {@link DataTable.defaults} but this
	 * one is the internal data store for DataTables's cache of columns. It should
	 * NOT be manipulated outside of DataTables. Any configuration should be done
	 * through the initialisation options.
	 *  @namespace
	 *  @todo Really should attach the settings object to individual instances so we
	 *    don't need to create new instances on each $().dataTable() call (if the
	 *    table already exists). It would also save passing oSettings around and
	 *    into every single function. However, this is a very significant
	 *    architecture change for DataTables and will almost certainly break
	 *    backwards compatibility with older installations. This is something that
	 *    will be done in 2.0.
	 */
	DataTable.models.oSettings = {
		/**
		 * Primary features of DataTables and their enablement state.
		 *  @namespace
		 */
		"oFeatures": {
	
			/**
			 * Flag to say if DataTables should automatically try to calculate the
			 * optimum table and columns widths (true) or not (false).
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bAutoWidth": null,
	
			/**
			 * Delay the creation of TR and TD elements until they are actually
			 * needed by a driven page draw. This can give a significant speed
			 * increase for Ajax source and Javascript source data, but makes no
			 * difference at all fro DOM and server-side processing tables.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bDeferRender": null,
	
			/**
			 * Enable filtering on the table or not. Note that if this is disabled
			 * then there is no filtering at all on the table, including fnFilter.
			 * To just remove the filtering input use sDom and remove the 'f' option.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bFilter": null,
	
			/**
			 * Table information element (the 'Showing x of y records' div) enable
			 * flag.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bInfo": null,
	
			/**
			 * Present a user control allowing the end user to change the page size
			 * when pagination is enabled.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bLengthChange": null,
	
			/**
			 * Pagination enabled or not. Note that if this is disabled then length
			 * changing must also be disabled.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bPaginate": null,
	
			/**
			 * Processing indicator enable flag whenever DataTables is enacting a
			 * user request - typically an Ajax request for server-side processing.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bProcessing": null,
	
			/**
			 * Server-side processing enabled flag - when enabled DataTables will
			 * get all data from the server for every draw - there is no filtering,
			 * sorting or paging done on the client-side.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bServerSide": null,
	
			/**
			 * Sorting enablement flag.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bSort": null,
	
			/**
			 * Multi-column sorting
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bSortMulti": null,
	
			/**
			 * Apply a class to the columns which are being sorted to provide a
			 * visual highlight or not. This can slow things down when enabled since
			 * there is a lot of DOM interaction.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bSortClasses": null,
	
			/**
			 * State saving enablement flag.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bStateSave": null
		},
	
	
		/**
		 * Scrolling settings for a table.
		 *  @namespace
		 */
		"oScroll": {
			/**
			 * When the table is shorter in height than sScrollY, collapse the
			 * table container down to the height of the table (when true).
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bCollapse": null,
	
			/**
			 * Width of the scrollbar for the web-browser's platform. Calculated
			 * during table initialisation.
			 *  @type int
			 *  @default 0
			 */
			"iBarWidth": 0,
	
			/**
			 * Viewport width for horizontal scrolling. Horizontal scrolling is
			 * disabled if an empty string.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type string
			 */
			"sX": null,
	
			/**
			 * Width to expand the table to when using x-scrolling. Typically you
			 * should not need to use this.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type string
			 *  @deprecated
			 */
			"sXInner": null,
	
			/**
			 * Viewport height for vertical scrolling. Vertical scrolling is disabled
			 * if an empty string.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type string
			 */
			"sY": null
		},
	
		/**
		 * Language information for the table.
		 *  @namespace
		 *  @extends DataTable.defaults.oLanguage
		 */
		"oLanguage": {
			/**
			 * Information callback function. See
			 * {@link DataTable.defaults.fnInfoCallback}
			 *  @type function
			 *  @default null
			 */
			"fnInfoCallback": null
		},
	
		/**
		 * Browser support parameters
		 *  @namespace
		 */
		"oBrowser": {
			/**
			 * Indicate if the browser incorrectly calculates width:100% inside a
			 * scrolling element (IE6/7)
			 *  @type boolean
			 *  @default false
			 */
			"bScrollOversize": false,
	
			/**
			 * Determine if the vertical scrollbar is on the right or left of the
			 * scrolling container - needed for rtl language layout, although not
			 * all browsers move the scrollbar (Safari).
			 *  @type boolean
			 *  @default false
			 */
			"bScrollbarLeft": false,
	
			/**
			 * Flag for if `getBoundingClientRect` is fully supported or not
			 *  @type boolean
			 *  @default false
			 */
			"bBounding": false,
	
			/**
			 * Browser scrollbar width
			 *  @type integer
			 *  @default 0
			 */
			"barWidth": 0
		},
	
	
		"ajax": null,
	
	
		/**
		 * Array referencing the nodes which are used for the features. The
		 * parameters of this object match what is allowed by sDom - i.e.
		 *   <ul>
		 *     <li>'l' - Length changing</li>
		 *     <li>'f' - Filtering input</li>
		 *     <li>'t' - The table!</li>
		 *     <li>'i' - Information</li>
		 *     <li>'p' - Pagination</li>
		 *     <li>'r' - pRocessing</li>
		 *   </ul>
		 *  @type array
		 *  @default []
		 */
		"aanFeatures": [],
	
		/**
		 * Store data information - see {@link DataTable.models.oRow} for detailed
		 * information.
		 *  @type array
		 *  @default []
		 */
		"aoData": [],
	
		/**
		 * Array of indexes which are in the current display (after filtering etc)
		 *  @type array
		 *  @default []
		 */
		"aiDisplay": [],
	
		/**
		 * Array of indexes for display - no filtering
		 *  @type array
		 *  @default []
		 */
		"aiDisplayMaster": [],
	
		/**
		 * Map of row ids to data indexes
		 *  @type object
		 *  @default {}
		 */
		"aIds": {},
	
		/**
		 * Store information about each column that is in use
		 *  @type array
		 *  @default []
		 */
		"aoColumns": [],
	
		/**
		 * Store information about the table's header
		 *  @type array
		 *  @default []
		 */
		"aoHeader": [],
	
		/**
		 * Store information about the table's footer
		 *  @type array
		 *  @default []
		 */
		"aoFooter": [],
	
		/**
		 * Store the applied global search information in case we want to force a
		 * research or compare the old search to a new one.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @namespace
		 *  @extends DataTable.models.oSearch
		 */
		"oPreviousSearch": {},
	
		/**
		 * Store the applied search for each column - see
		 * {@link DataTable.models.oSearch} for the format that is used for the
		 * filtering information for each column.
		 *  @type array
		 *  @default []
		 */
		"aoPreSearchCols": [],
	
		/**
		 * Sorting that is applied to the table. Note that the inner arrays are
		 * used in the following manner:
		 * <ul>
		 *   <li>Index 0 - column number</li>
		 *   <li>Index 1 - current sorting direction</li>
		 * </ul>
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type array
		 *  @todo These inner arrays should really be objects
		 */
		"aaSorting": null,
	
		/**
		 * Sorting that is always applied to the table (i.e. prefixed in front of
		 * aaSorting).
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type array
		 *  @default []
		 */
		"aaSortingFixed": [],
	
		/**
		 * Classes to use for the striping of a table.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type array
		 *  @default []
		 */
		"asStripeClasses": null,
	
		/**
		 * If restoring a table - we should restore its striping classes as well
		 *  @type array
		 *  @default []
		 */
		"asDestroyStripes": [],
	
		/**
		 * If restoring a table - we should restore its width
		 *  @type int
		 *  @default 0
		 */
		"sDestroyWidth": 0,
	
		/**
		 * Callback functions array for every time a row is inserted (i.e. on a draw).
		 *  @type array
		 *  @default []
		 */
		"aoRowCallback": [],
	
		/**
		 * Callback functions for the header on each draw.
		 *  @type array
		 *  @default []
		 */
		"aoHeaderCallback": [],
	
		/**
		 * Callback function for the footer on each draw.
		 *  @type array
		 *  @default []
		 */
		"aoFooterCallback": [],
	
		/**
		 * Array of callback functions for draw callback functions
		 *  @type array
		 *  @default []
		 */
		"aoDrawCallback": [],
	
		/**
		 * Array of callback functions for row created function
		 *  @type array
		 *  @default []
		 */
		"aoRowCreatedCallback": [],
	
		/**
		 * Callback functions for just before the table is redrawn. A return of
		 * false will be used to cancel the draw.
		 *  @type array
		 *  @default []
		 */
		"aoPreDrawCallback": [],
	
		/**
		 * Callback functions for when the table has been initialised.
		 *  @type array
		 *  @default []
		 */
		"aoInitComplete": [],
	
	
		/**
		 * Callbacks for modifying the settings to be stored for state saving, prior to
		 * saving state.
		 *  @type array
		 *  @default []
		 */
		"aoStateSaveParams": [],
	
		/**
		 * Callbacks for modifying the settings that have been stored for state saving
		 * prior to using the stored values to restore the state.
		 *  @type array
		 *  @default []
		 */
		"aoStateLoadParams": [],
	
		/**
		 * Callbacks for operating on the settings object once the saved state has been
		 * loaded
		 *  @type array
		 *  @default []
		 */
		"aoStateLoaded": [],
	
		/**
		 * Cache the table ID for quick access
		 *  @type string
		 *  @default <i>Empty string</i>
		 */
		"sTableId": "",
	
		/**
		 * The TABLE node for the main table
		 *  @type node
		 *  @default null
		 */
		"nTable": null,
	
		/**
		 * Permanent ref to the thead element
		 *  @type node
		 *  @default null
		 */
		"nTHead": null,
	
		/**
		 * Permanent ref to the tfoot element - if it exists
		 *  @type node
		 *  @default null
		 */
		"nTFoot": null,
	
		/**
		 * Permanent ref to the tbody element
		 *  @type node
		 *  @default null
		 */
		"nTBody": null,
	
		/**
		 * Cache the wrapper node (contains all DataTables controlled elements)
		 *  @type node
		 *  @default null
		 */
		"nTableWrapper": null,
	
		/**
		 * Indicate if when using server-side processing the loading of data
		 * should be deferred until the second draw.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type boolean
		 *  @default false
		 */
		"bDeferLoading": false,
	
		/**
		 * Indicate if all required information has been read in
		 *  @type boolean
		 *  @default false
		 */
		"bInitialised": false,
	
		/**
		 * Information about open rows. Each object in the array has the parameters
		 * 'nTr' and 'nParent'
		 *  @type array
		 *  @default []
		 */
		"aoOpenRows": [],
	
		/**
		 * Dictate the positioning of DataTables' control elements - see
		 * {@link DataTable.model.oInit.sDom}.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type string
		 *  @default null
		 */
		"sDom": null,
	
		/**
		 * Search delay (in mS)
		 *  @type integer
		 *  @default null
		 */
		"searchDelay": null,
	
		/**
		 * Which type of pagination should be used.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type string
		 *  @default two_button
		 */
		"sPaginationType": "two_button",
	
		/**
		 * The state duration (for `stateSave`) in seconds.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type int
		 *  @default 0
		 */
		"iStateDuration": 0,
	
		/**
		 * Array of callback functions for state saving. Each array element is an
		 * object with the following parameters:
		 *   <ul>
		 *     <li>function:fn - function to call. Takes two parameters, oSettings
		 *       and the JSON string to save that has been thus far created. Returns
		 *       a JSON string to be inserted into a json object
		 *       (i.e. '"param": [ 0, 1, 2]')</li>
		 *     <li>string:sName - name of callback</li>
		 *   </ul>
		 *  @type array
		 *  @default []
		 */
		"aoStateSave": [],
	
		/**
		 * Array of callback functions for state loading. Each array element is an
		 * object with the following parameters:
		 *   <ul>
		 *     <li>function:fn - function to call. Takes two parameters, oSettings
		 *       and the object stored. May return false to cancel state loading</li>
		 *     <li>string:sName - name of callback</li>
		 *   </ul>
		 *  @type array
		 *  @default []
		 */
		"aoStateLoad": [],
	
		/**
		 * State that was saved. Useful for back reference
		 *  @type object
		 *  @default null
		 */
		"oSavedState": null,
	
		/**
		 * State that was loaded. Useful for back reference
		 *  @type object
		 *  @default null
		 */
		"oLoadedState": null,
	
		/**
		 * Source url for AJAX data for the table.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type string
		 *  @default null
		 */
		"sAjaxSource": null,
	
		/**
		 * Property from a given object from which to read the table data from. This
		 * can be an empty string (when not server-side processing), in which case
		 * it is  assumed an an array is given directly.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type string
		 */
		"sAjaxDataProp": null,
	
		/**
		 * Note if draw should be blocked while getting data
		 *  @type boolean
		 *  @default true
		 */
		"bAjaxDataGet": true,
	
		/**
		 * The last jQuery XHR object that was used for server-side data gathering.
		 * This can be used for working with the XHR information in one of the
		 * callbacks
		 *  @type object
		 *  @default null
		 */
		"jqXHR": null,
	
		/**
		 * JSON returned from the server in the last Ajax request
		 *  @type object
		 *  @default undefined
		 */
		"json": undefined,
	
		/**
		 * Data submitted as part of the last Ajax request
		 *  @type object
		 *  @default undefined
		 */
		"oAjaxData": undefined,
	
		/**
		 * Function to get the server-side data.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type function
		 */
		"fnServerData": null,
	
		/**
		 * Functions which are called prior to sending an Ajax request so extra
		 * parameters can easily be sent to the server
		 *  @type array
		 *  @default []
		 */
		"aoServerParams": [],
	
		/**
		 * Send the XHR HTTP method - GET or POST (could be PUT or DELETE if
		 * required).
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type string
		 */
		"sServerMethod": null,
	
		/**
		 * Format numbers for display.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type function
		 */
		"fnFormatNumber": null,
	
		/**
		 * List of options that can be used for the user selectable length menu.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type array
		 *  @default []
		 */
		"aLengthMenu": null,
	
		/**
		 * Counter for the draws that the table does. Also used as a tracker for
		 * server-side processing
		 *  @type int
		 *  @default 0
		 */
		"iDraw": 0,
	
		/**
		 * Indicate if a redraw is being done - useful for Ajax
		 *  @type boolean
		 *  @default false
		 */
		"bDrawing": false,
	
		/**
		 * Draw index (iDraw) of the last error when parsing the returned data
		 *  @type int
		 *  @default -1
		 */
		"iDrawError": -1,
	
		/**
		 * Paging display length
		 *  @type int
		 *  @default 10
		 */
		"_iDisplayLength": 10,
	
		/**
		 * Paging start point - aiDisplay index
		 *  @type int
		 *  @default 0
		 */
		"_iDisplayStart": 0,
	
		/**
		 * Server-side processing - number of records in the result set
		 * (i.e. before filtering), Use fnRecordsTotal rather than
		 * this property to get the value of the number of records, regardless of
		 * the server-side processing setting.
		 *  @type int
		 *  @default 0
		 *  @private
		 */
		"_iRecordsTotal": 0,
	
		/**
		 * Server-side processing - number of records in the current display set
		 * (i.e. after filtering). Use fnRecordsDisplay rather than
		 * this property to get the value of the number of records, regardless of
		 * the server-side processing setting.
		 *  @type boolean
		 *  @default 0
		 *  @private
		 */
		"_iRecordsDisplay": 0,
	
		/**
		 * Flag to indicate if jQuery UI marking and classes should be used.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type boolean
		 */
		"bJUI": null,
	
		/**
		 * The classes to use for the table
		 *  @type object
		 *  @default {}
		 */
		"oClasses": {},
	
		/**
		 * Flag attached to the settings object so you can check in the draw
		 * callback if filtering has been done in the draw. Deprecated in favour of
		 * events.
		 *  @type boolean
		 *  @default false
		 *  @deprecated
		 */
		"bFiltered": false,
	
		/**
		 * Flag attached to the settings object so you can check in the draw
		 * callback if sorting has been done in the draw. Deprecated in favour of
		 * events.
		 *  @type boolean
		 *  @default false
		 *  @deprecated
		 */
		"bSorted": false,
	
		/**
		 * Indicate that if multiple rows are in the header and there is more than
		 * one unique cell per column, if the top one (true) or bottom one (false)
		 * should be used for sorting / title by DataTables.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type boolean
		 */
		"bSortCellsTop": null,
	
		/**
		 * Initialisation object that is used for the table
		 *  @type object
		 *  @default null
		 */
		"oInit": null,
	
		/**
		 * Destroy callback functions - for plug-ins to attach themselves to the
		 * destroy so they can clean up markup and events.
		 *  @type array
		 *  @default []
		 */
		"aoDestroyCallback": [],
	
	
		/**
		 * Get the number of records in the current record set, before filtering
		 *  @type function
		 */
		"fnRecordsTotal": function ()
		{
			return _fnDataSource( this ) == 'ssp' ?
				this._iRecordsTotal * 1 :
				this.aiDisplayMaster.length;
		},
	
		/**
		 * Get the number of records in the current record set, after filtering
		 *  @type function
		 */
		"fnRecordsDisplay": function ()
		{
			return _fnDataSource( this ) == 'ssp' ?
				this._iRecordsDisplay * 1 :
				this.aiDisplay.length;
		},
	
		/**
		 * Get the display end point - aiDisplay index
		 *  @type function
		 */
		"fnDisplayEnd": function ()
		{
			var
				len      = this._iDisplayLength,
				start    = this._iDisplayStart,
				calc     = start + len,
				records  = this.aiDisplay.length,
				features = this.oFeatures,
				paginate = features.bPaginate;
	
			if ( features.bServerSide ) {
				return paginate === false || len === -1 ?
					start + records :
					Math.min( start+len, this._iRecordsDisplay );
			}
			else {
				return ! paginate || calc>records || len===-1 ?
					records :
					calc;
			}
		},
	
		/**
		 * The DataTables object for this table
		 *  @type object
		 *  @default null
		 */
		"oInstance": null,
	
		/**
		 * Unique identifier for each instance of the DataTables object. If there
		 * is an ID on the table node, then it takes that value, otherwise an
		 * incrementing internal counter is used.
		 *  @type string
		 *  @default null
		 */
		"sInstance": null,
	
		/**
		 * tabindex attribute value that is added to DataTables control elements, allowing
		 * keyboard navigation of the table and its controls.
		 */
		"iTabIndex": 0,
	
		/**
		 * DIV container for the footer scrolling table if scrolling
		 */
		"nScrollHead": null,
	
		/**
		 * DIV container for the footer scrolling table if scrolling
		 */
		"nScrollFoot": null,
	
		/**
		 * Last applied sort
		 *  @type array
		 *  @default []
		 */
		"aLastSort": [],
	
		/**
		 * Stored plug-in instances
		 *  @type object
		 *  @default {}
		 */
		"oPlugins": {},
	
		/**
		 * Function used to get a row's id from the row's data
		 *  @type function
		 *  @default null
		 */
		"rowIdFn": null,
	
		/**
		 * Data location where to store a row's id
		 *  @type string
		 *  @default null
		 */
		"rowId": null
	};

	/**
	 * Extension object for DataTables that is used to provide all extension
	 * options.
	 *
	 * Note that the `DataTable.ext` object is available through
	 * `jQuery.fn.dataTable.ext` where it may be accessed and manipulated. It is
	 * also aliased to `jQuery.fn.dataTableExt` for historic reasons.
	 *  @namespace
	 *  @extends DataTable.models.ext
	 */
	
	
	/**
	 * DataTables extensions
	 * 
	 * This namespace acts as a collection area for plug-ins that can be used to
	 * extend DataTables capabilities. Indeed many of the build in methods
	 * use this method to provide their own capabilities (sorting methods for
	 * example).
	 *
	 * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy
	 * reasons
	 *
	 *  @namespace
	 */
	DataTable.ext = _ext = {
		/**
		 * Buttons. For use with the Buttons extension for DataTables. This is
		 * defined here so other extensions can define buttons regardless of load
		 * order. It is _not_ used by DataTables core.
		 *
		 *  @type object
		 *  @default {}
		 */
		buttons: {},
	
	
		/**
		 * Element class names
		 *
		 *  @type object
		 *  @default {}
		 */
		classes: {},
	
	
		/**
		 * DataTables build type (expanded by the download builder)
		 *
		 *  @type string
		 */
		build:"dt/dt-1.10.12,af-2.1.2,b-1.2.1,b-colvis-1.2.1,cr-1.3.2,fc-3.2.2,fh-3.1.2,kt-2.1.2,r-2.1.0,rr-1.1.2,sc-1.4.2,se-1.2.0",
	
	
		/**
		 * Error reporting.
		 * 
		 * How should DataTables report an error. Can take the value 'alert',
		 * 'throw', 'none' or a function.
		 *
		 *  @type string|function
		 *  @default alert
		 */
		errMode: "alert",
	
	
		/**
		 * Feature plug-ins.
		 * 
		 * This is an array of objects which describe the feature plug-ins that are
		 * available to DataTables. These feature plug-ins are then available for
		 * use through the `dom` initialisation option.
		 * 
		 * Each feature plug-in is described by an object which must have the
		 * following properties:
		 * 
		 * * `fnInit` - function that is used to initialise the plug-in,
		 * * `cFeature` - a character so the feature can be enabled by the `dom`
		 *   instillation option. This is case sensitive.
		 *
		 * The `fnInit` function has the following input parameters:
		 *
		 * 1. `{object}` DataTables settings object: see
		 *    {@link DataTable.models.oSettings}
		 *
		 * And the following return is expected:
		 * 
		 * * {node|null} The element which contains your feature. Note that the
		 *   return may also be void if your plug-in does not require to inject any
		 *   DOM elements into DataTables control (`dom`) - for example this might
		 *   be useful when developing a plug-in which allows table control via
		 *   keyboard entry
		 *
		 *  @type array
		 *
		 *  @example
		 *    $.fn.dataTable.ext.features.push( {
		 *      "fnInit": function( oSettings ) {
		 *        return new TableTools( { "oDTSettings": oSettings } );
		 *      },
		 *      "cFeature": "T"
		 *    } );
		 */
		feature: [],
	
	
		/**
		 * Row searching.
		 * 
		 * This method of searching is complimentary to the default type based
		 * searching, and a lot more comprehensive as it allows you complete control
		 * over the searching logic. Each element in this array is a function
		 * (parameters described below) that is called for every row in the table,
		 * and your logic decides if it should be included in the searching data set
		 * or not.
		 *
		 * Searching functions have the following input parameters:
		 *
		 * 1. `{object}` DataTables settings object: see
		 *    {@link DataTable.models.oSettings}
		 * 2. `{array|object}` Data for the row to be processed (same as the
		 *    original format that was passed in as the data source, or an array
		 *    from a DOM data source
		 * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which
		 *    can be useful to retrieve the `TR` element if you need DOM interaction.
		 *
		 * And the following return is expected:
		 *
		 * * {boolean} Include the row in the searched result set (true) or not
		 *   (false)
		 *
		 * Note that as with the main search ability in DataTables, technically this
		 * is "filtering", since it is subtractive. However, for consistency in
		 * naming we call it searching here.
		 *
		 *  @type array
		 *  @default []
		 *
		 *  @example
		 *    // The following example shows custom search being applied to the
		 *    // fourth column (i.e. the data[3] index) based on two input values
		 *    // from the end-user, matching the data in a certain range.
		 *    $.fn.dataTable.ext.search.push(
		 *      function( settings, data, dataIndex ) {
		 *        var min = document.getElementById('min').value * 1;
		 *        var max = document.getElementById('max').value * 1;
		 *        var version = data[3] == "-" ? 0 : data[3]*1;
		 *
		 *        if ( min == "" && max == "" ) {
		 *          return true;
		 *        }
		 *        else if ( min == "" && version < max ) {
		 *          return true;
		 *        }
		 *        else if ( min < version && "" == max ) {
		 *          return true;
		 *        }
		 *        else if ( min < version && version < max ) {
		 *          return true;
		 *        }
		 *        return false;
		 *      }
		 *    );
		 */
		search: [],
	
	
		/**
		 * Selector extensions
		 *
		 * The `selector` option can be used to extend the options available for the
		 * selector modifier options (`selector-modifier` object data type) that
		 * each of the three built in selector types offer (row, column and cell +
		 * their plural counterparts). For example the Select extension uses this
		 * mechanism to provide an option to select only rows, columns and cells
		 * that have been marked as selected by the end user (`{selected: true}`),
		 * which can be used in conjunction with the existing built in selector
		 * options.
		 *
		 * Each property is an array to which functions can be pushed. The functions
		 * take three attributes:
		 *
		 * * Settings object for the host table
		 * * Options object (`selector-modifier` object type)
		 * * Array of selected item indexes
		 *
		 * The return is an array of the resulting item indexes after the custom
		 * selector has been applied.
		 *
		 *  @type object
		 */
		selector: {
			cell: [],
			column: [],
			row: []
		},
	
	
		/**
		 * Internal functions, exposed for used in plug-ins.
		 * 
		 * Please note that you should not need to use the internal methods for
		 * anything other than a plug-in (and even then, try to avoid if possible).
		 * The internal function may change between releases.
		 *
		 *  @type object
		 *  @default {}
		 */
		internal: {},
	
	
		/**
		 * Legacy configuration options. Enable and disable legacy options that
		 * are available in DataTables.
		 *
		 *  @type object
		 */
		legacy: {
			/**
			 * Enable / disable DataTables 1.9 compatible server-side processing
			 * requests
			 *
			 *  @type boolean
			 *  @default null
			 */
			ajax: null
		},
	
	
		/**
		 * Pagination plug-in methods.
		 * 
		 * Each entry in this object is a function and defines which buttons should
		 * be shown by the pagination rendering method that is used for the table:
		 * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the
		 * buttons are displayed in the document, while the functions here tell it
		 * what buttons to display. This is done by returning an array of button
		 * descriptions (what each button will do).
		 *
		 * Pagination types (the four built in options and any additional plug-in
		 * options defined here) can be used through the `paginationType`
		 * initialisation parameter.
		 *
		 * The functions defined take two parameters:
		 *
		 * 1. `{int} page` The current page index
		 * 2. `{int} pages` The number of pages in the table
		 *
		 * Each function is expected to return an array where each element of the
		 * array can be one of:
		 *
		 * * `first` - Jump to first page when activated
		 * * `last` - Jump to last page when activated
		 * * `previous` - Show previous page when activated
		 * * `next` - Show next page when activated
		 * * `{int}` - Show page of the index given
		 * * `{array}` - A nested array containing the above elements to add a
		 *   containing 'DIV' element (might be useful for styling).
		 *
		 * Note that DataTables v1.9- used this object slightly differently whereby
		 * an object with two functions would be defined for each plug-in. That
		 * ability is still supported by DataTables 1.10+ to provide backwards
		 * compatibility, but this option of use is now decremented and no longer
		 * documented in DataTables 1.10+.
		 *
		 *  @type object
		 *  @default {}
		 *
		 *  @example
		 *    // Show previous, next and current page buttons only
		 *    $.fn.dataTableExt.oPagination.current = function ( page, pages ) {
		 *      return [ 'previous', page, 'next' ];
		 *    };
		 */
		pager: {},
	
	
		renderer: {
			pageButton: {},
			header: {}
		},
	
	
		/**
		 * Ordering plug-ins - custom data source
		 * 
		 * The extension options for ordering of data available here is complimentary
		 * to the default type based ordering that DataTables typically uses. It
		 * allows much greater control over the the data that is being used to
		 * order a column, but is necessarily therefore more complex.
		 * 
		 * This type of ordering is useful if you want to do ordering based on data
		 * live from the DOM (for example the contents of an 'input' element) rather
		 * than just the static string that DataTables knows of.
		 * 
		 * The way these plug-ins work is that you create an array of the values you
		 * wish to be ordering for the column in question and then return that
		 * array. The data in the array much be in the index order of the rows in
		 * the table (not the currently ordering order!). Which order data gathering
		 * function is run here depends on the `dt-init columns.orderDataType`
		 * parameter that is used for the column (if any).
		 *
		 * The functions defined take two parameters:
		 *
		 * 1. `{object}` DataTables settings object: see
		 *    {@link DataTable.models.oSettings}
		 * 2. `{int}` Target column index
		 *
		 * Each function is expected to return an array:
		 *
		 * * `{array}` Data for the column to be ordering upon
		 *
		 *  @type array
		 *
		 *  @example
		 *    // Ordering using `input` node values
		 *    $.fn.dataTable.ext.order['dom-text'] = function  ( settings, col )
		 *    {
		 *      return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) {
		 *        return $('input', td).val();
		 *      } );
		 *    }
		 */
		order: {},
	
	
		/**
		 * Type based plug-ins.
		 *
		 * Each column in DataTables has a type assigned to it, either by automatic
		 * detection or by direct assignment using the `type` option for the column.
		 * The type of a column will effect how it is ordering and search (plug-ins
		 * can also make use of the column type if required).
		 *
		 * @namespace
		 */
		type: {
			/**
			 * Type detection functions.
			 *
			 * The functions defined in this object are used to automatically detect
			 * a column's type, making initialisation of DataTables super easy, even
			 * when complex data is in the table.
			 *
			 * The functions defined take two parameters:
			 *
		     *  1. `{*}` Data from the column cell to be analysed
		     *  2. `{settings}` DataTables settings object. This can be used to
		     *     perform context specific type detection - for example detection
		     *     based on language settings such as using a comma for a decimal
		     *     place. Generally speaking the options from the settings will not
		     *     be required
			 *
			 * Each function is expected to return:
			 *
			 * * `{string|null}` Data type detected, or null if unknown (and thus
			 *   pass it on to the other type detection functions.
			 *
			 *  @type array
			 *
			 *  @example
			 *    // Currency type detection plug-in:
			 *    $.fn.dataTable.ext.type.detect.push(
			 *      function ( data, settings ) {
			 *        // Check the numeric part
			 *        if ( ! $.isNumeric( data.substring(1) ) ) {
			 *          return null;
			 *        }
			 *
			 *        // Check prefixed by currency
			 *        if ( data.charAt(0) == '$' || data.charAt(0) == '&pound;' ) {
			 *          return 'currency';
			 *        }
			 *        return null;
			 *      }
			 *    );
			 */
			detect: [],
	
	
			/**
			 * Type based search formatting.
			 *
			 * The type based searching functions can be used to pre-format the
			 * data to be search on. For example, it can be used to strip HTML
			 * tags or to de-format telephone numbers for numeric only searching.
			 *
			 * Note that is a search is not defined for a column of a given type,
			 * no search formatting will be performed.
			 * 
			 * Pre-processing of searching data plug-ins - When you assign the sType
			 * for a column (or have it automatically detected for you by DataTables
			 * or a type detection plug-in), you will typically be using this for
			 * custom sorting, but it can also be used to provide custom searching
			 * by allowing you to pre-processing the data and returning the data in
			 * the format that should be searched upon. This is done by adding
			 * functions this object with a parameter name which matches the sType
			 * for that target column. This is the corollary of <i>afnSortData</i>
			 * for searching data.
			 *
			 * The functions defined take a single parameter:
			 *
		     *  1. `{*}` Data from the column cell to be prepared for searching
			 *
			 * Each function is expected to return:
			 *
			 * * `{string|null}` Formatted string that will be used for the searching.
			 *
			 *  @type object
			 *  @default {}
			 *
			 *  @example
			 *    $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) {
			 *      return d.replace(/\n/g," ").replace( /<.*?>/g, "" );
			 *    }
			 */
			search: {},
	
	
			/**
			 * Type based ordering.
			 *
			 * The column type tells DataTables what ordering to apply to the table
			 * when a column is sorted upon. The order for each type that is defined,
			 * is defined by the functions available in this object.
			 *
			 * Each ordering option can be described by three properties added to
			 * this object:
			 *
			 * * `{type}-pre` - Pre-formatting function
			 * * `{type}-asc` - Ascending order function
			 * * `{type}-desc` - Descending order function
			 *
			 * All three can be used together, only `{type}-pre` or only
			 * `{type}-asc` and `{type}-desc` together. It is generally recommended
			 * that only `{type}-pre` is used, as this provides the optimal
			 * implementation in terms of speed, although the others are provided
			 * for compatibility with existing Javascript sort functions.
			 *
			 * `{type}-pre`: Functions defined take a single parameter:
			 *
		     *  1. `{*}` Data from the column cell to be prepared for ordering
			 *
			 * And return:
			 *
			 * * `{*}` Data to be sorted upon
			 *
			 * `{type}-asc` and `{type}-desc`: Functions are typical Javascript sort
			 * functions, taking two parameters:
			 *
		     *  1. `{*}` Data to compare to the second parameter
		     *  2. `{*}` Data to compare to the first parameter
			 *
			 * And returning:
			 *
			 * * `{*}` Ordering match: <0 if first parameter should be sorted lower
			 *   than the second parameter, ===0 if the two parameters are equal and
			 *   >0 if the first parameter should be sorted height than the second
			 *   parameter.
			 * 
			 *  @type object
			 *  @default {}
			 *
			 *  @example
			 *    // Numeric ordering of formatted numbers with a pre-formatter
			 *    $.extend( $.fn.dataTable.ext.type.order, {
			 *      "string-pre": function(x) {
			 *        a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" );
			 *        return parseFloat( a );
			 *      }
			 *    } );
			 *
			 *  @example
			 *    // Case-sensitive string ordering, with no pre-formatting method
			 *    $.extend( $.fn.dataTable.ext.order, {
			 *      "string-case-asc": function(x,y) {
			 *        return ((x < y) ? -1 : ((x > y) ? 1 : 0));
			 *      },
			 *      "string-case-desc": function(x,y) {
			 *        return ((x < y) ? 1 : ((x > y) ? -1 : 0));
			 *      }
			 *    } );
			 */
			order: {}
		},
	
		/**
		 * Unique DataTables instance counter
		 *
		 * @type int
		 * @private
		 */
		_unique: 0,
	
	
		//
		// Depreciated
		// The following properties are retained for backwards compatiblity only.
		// The should not be used in new projects and will be removed in a future
		// version
		//
	
		/**
		 * Version check function.
		 *  @type function
		 *  @depreciated Since 1.10
		 */
		fnVersionCheck: DataTable.fnVersionCheck,
	
	
		/**
		 * Index for what 'this' index API functions should use
		 *  @type int
		 *  @deprecated Since v1.10
		 */
		iApiIndex: 0,
	
	
		/**
		 * jQuery UI class container
		 *  @type object
		 *  @deprecated Since v1.10
		 */
		oJUIClasses: {},
	
	
		/**
		 * Software version
		 *  @type string
		 *  @deprecated Since v1.10
		 */
		sVersion: DataTable.version
	};
	
	
	//
	// Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts
	//
	$.extend( _ext, {
		afnFiltering: _ext.search,
		aTypes:       _ext.type.detect,
		ofnSearch:    _ext.type.search,
		oSort:        _ext.type.order,
		afnSortData:  _ext.order,
		aoFeatures:   _ext.feature,
		oApi:         _ext.internal,
		oStdClasses:  _ext.classes,
		oPagination:  _ext.pager
	} );
	
	
	$.extend( DataTable.ext.classes, {
		"sTable": "dataTable",
		"sNoFooter": "no-footer",
	
		/* Paging buttons */
		"sPageButton": "paginate_button",
		"sPageButtonActive": "current",
		"sPageButtonDisabled": "disabled",
	
		/* Striping classes */
		"sStripeOdd": "odd",
		"sStripeEven": "even",
	
		/* Empty row */
		"sRowEmpty": "dataTables_empty",
	
		/* Features */
		"sWrapper": "dataTables_wrapper",
		"sFilter": "dataTables_filter",
		"sInfo": "dataTables_info",
		"sPaging": "dataTables_paginate paging_", /* Note that the type is postfixed */
		"sLength": "dataTables_length",
		"sProcessing": "dataTables_processing",
	
		/* Sorting */
		"sSortAsc": "sorting_asc",
		"sSortDesc": "sorting_desc",
		"sSortable": "sorting", /* Sortable in both directions */
		"sSortableAsc": "sorting_asc_disabled",
		"sSortableDesc": "sorting_desc_disabled",
		"sSortableNone": "sorting_disabled",
		"sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */
	
		/* Filtering */
		"sFilterInput": "",
	
		/* Page length */
		"sLengthSelect": "",
	
		/* Scrolling */
		"sScrollWrapper": "dataTables_scroll",
		"sScrollHead": "dataTables_scrollHead",
		"sScrollHeadInner": "dataTables_scrollHeadInner",
		"sScrollBody": "dataTables_scrollBody",
		"sScrollFoot": "dataTables_scrollFoot",
		"sScrollFootInner": "dataTables_scrollFootInner",
	
		/* Misc */
		"sHeaderTH": "",
		"sFooterTH": "",
	
		// Deprecated
		"sSortJUIAsc": "",
		"sSortJUIDesc": "",
		"sSortJUI": "",
		"sSortJUIAscAllowed": "",
		"sSortJUIDescAllowed": "",
		"sSortJUIWrapper": "",
		"sSortIcon": "",
		"sJUIHeader": "",
		"sJUIFooter": ""
	} );
	
	
	(function() {
	
	// Reused strings for better compression. Closure compiler appears to have a
	// weird edge case where it is trying to expand strings rather than use the
	// variable version. This results in about 200 bytes being added, for very
	// little preference benefit since it this run on script load only.
	var _empty = '';
	_empty = '';
	
	var _stateDefault = _empty + 'ui-state-default';
	var _sortIcon     = _empty + 'css_right ui-icon ui-icon-';
	var _headerFooter = _empty + 'fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix';
	
	$.extend( DataTable.ext.oJUIClasses, DataTable.ext.classes, {
		/* Full numbers paging buttons */
		"sPageButton":         "fg-button ui-button "+_stateDefault,
		"sPageButtonActive":   "ui-state-disabled",
		"sPageButtonDisabled": "ui-state-disabled",
	
		/* Features */
		"sPaging": "dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi "+
			"ui-buttonset-multi paging_", /* Note that the type is postfixed */
	
		/* Sorting */
		"sSortAsc":            _stateDefault+" sorting_asc",
		"sSortDesc":           _stateDefault+" sorting_desc",
		"sSortable":           _stateDefault+" sorting",
		"sSortableAsc":        _stateDefault+" sorting_asc_disabled",
		"sSortableDesc":       _stateDefault+" sorting_desc_disabled",
		"sSortableNone":       _stateDefault+" sorting_disabled",
		"sSortJUIAsc":         _sortIcon+"triangle-1-n",
		"sSortJUIDesc":        _sortIcon+"triangle-1-s",
		"sSortJUI":            _sortIcon+"carat-2-n-s",
		"sSortJUIAscAllowed":  _sortIcon+"carat-1-n",
		"sSortJUIDescAllowed": _sortIcon+"carat-1-s",
		"sSortJUIWrapper":     "DataTables_sort_wrapper",
		"sSortIcon":           "DataTables_sort_icon",
	
		/* Scrolling */
		"sScrollHead": "dataTables_scrollHead "+_stateDefault,
		"sScrollFoot": "dataTables_scrollFoot "+_stateDefault,
	
		/* Misc */
		"sHeaderTH":  _stateDefault,
		"sFooterTH":  _stateDefault,
		"sJUIHeader": _headerFooter+" ui-corner-tl ui-corner-tr",
		"sJUIFooter": _headerFooter+" ui-corner-bl ui-corner-br"
	} );
	
	}());
	
	
	
	var extPagination = DataTable.ext.pager;
	
	function _numbers ( page, pages ) {
		var
			numbers = [],
			buttons = extPagination.numbers_length,
			half = Math.floor( buttons / 2 ),
			i = 1;
	
		if ( pages <= buttons ) {
			numbers = _range( 0, pages );
		}
		else if ( page <= half ) {
			numbers = _range( 0, buttons-2 );
			numbers.push( 'ellipsis' );
			numbers.push( pages-1 );
		}
		else if ( page >= pages - 1 - half ) {
			numbers = _range( pages-(buttons-2), pages );
			numbers.splice( 0, 0, 'ellipsis' ); // no unshift in ie6
			numbers.splice( 0, 0, 0 );
		}
		else {
			numbers = _range( page-half+2, page+half-1 );
			numbers.push( 'ellipsis' );
			numbers.push( pages-1 );
			numbers.splice( 0, 0, 'ellipsis' );
			numbers.splice( 0, 0, 0 );
		}
	
		numbers.DT_el = 'span';
		return numbers;
	}
	
	
	$.extend( extPagination, {
		simple: function ( page, pages ) {
			return [ 'previous', 'next' ];
		},
	
		full: function ( page, pages ) {
			return [  'first', 'previous', 'next', 'last' ];
		},
	
		numbers: function ( page, pages ) {
			return [ _numbers(page, pages) ];
		},
	
		simple_numbers: function ( page, pages ) {
			return [ 'previous', _numbers(page, pages), 'next' ];
		},
	
		full_numbers: function ( page, pages ) {
			return [ 'first', 'previous', _numbers(page, pages), 'next', 'last' ];
		},
	
		// For testing and plug-ins to use
		_numbers: _numbers,
	
		// Number of number buttons (including ellipsis) to show. _Must be odd!_
		numbers_length: 7
	} );
	
	
	$.extend( true, DataTable.ext.renderer, {
		pageButton: {
			_: function ( settings, host, idx, buttons, page, pages ) {
				var classes = settings.oClasses;
				var lang = settings.oLanguage.oPaginate;
				var aria = settings.oLanguage.oAria.paginate || {};
				var btnDisplay, btnClass, counter=0;
	
				var attach = function( container, buttons ) {
					var i, ien, node, button;
					var clickHandler = function ( e ) {
						_fnPageChange( settings, e.data.action, true );
					};
	
					for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
						button = buttons[i];
	
						if ( $.isArray( button ) ) {
							var inner = $( '<'+(button.DT_el || 'div')+'/>' )
								.appendTo( container );
							attach( inner, button );
						}
						else {
							btnDisplay = null;
							btnClass = '';
	
							switch ( button ) {
								case 'ellipsis':
									container.append('<span class="ellipsis">&#x2026;</span>');
									break;
	
								case 'first':
									btnDisplay = lang.sFirst;
									btnClass = button + (page > 0 ?
										'' : ' '+classes.sPageButtonDisabled);
									break;
	
								case 'previous':
									btnDisplay = lang.sPrevious;
									btnClass = button + (page > 0 ?
										'' : ' '+classes.sPageButtonDisabled);
									break;
	
								case 'next':
									btnDisplay = lang.sNext;
									btnClass = button + (page < pages-1 ?
										'' : ' '+classes.sPageButtonDisabled);
									break;
	
								case 'last':
									btnDisplay = lang.sLast;
									btnClass = button + (page < pages-1 ?
										'' : ' '+classes.sPageButtonDisabled);
									break;
	
								default:
									btnDisplay = button + 1;
									btnClass = page === button ?
										classes.sPageButtonActive : '';
									break;
							}
	
							if ( btnDisplay !== null ) {
								node = $('<a>', {
										'class': classes.sPageButton+' '+btnClass,
										'aria-controls': settings.sTableId,
										'aria-label': aria[ button ],
										'data-dt-idx': counter,
										'tabindex': settings.iTabIndex,
										'id': idx === 0 && typeof button === 'string' ?
											settings.sTableId +'_'+ button :
											null
									} )
									.html( btnDisplay )
									.appendTo( container );
	
								_fnBindAction(
									node, {action: button}, clickHandler
								);
	
								counter++;
							}
						}
					}
				};
	
				// IE9 throws an 'unknown error' if document.activeElement is used
				// inside an iframe or frame. Try / catch the error. Not good for
				// accessibility, but neither are frames.
				var activeEl;
	
				try {
					// Because this approach is destroying and recreating the paging
					// elements, focus is lost on the select button which is bad for
					// accessibility. So we want to restore focus once the draw has
					// completed
					activeEl = $(host).find(document.activeElement).data('dt-idx');
				}
				catch (e) {}
	
				attach( $(host).empty(), buttons );
	
				if ( activeEl ) {
					$(host).find( '[data-dt-idx='+activeEl+']' ).focus();
				}
			}
		}
	} );
	
	
	
	// Built in type detection. See model.ext.aTypes for information about
	// what is required from this methods.
	$.extend( DataTable.ext.type.detect, [
		// Plain numbers - first since V8 detects some plain numbers as dates
		// e.g. Date.parse('55') (but not all, e.g. Date.parse('22')...).
		function ( d, settings )
		{
			var decimal = settings.oLanguage.sDecimal;
			return _isNumber( d, decimal ) ? 'num'+decimal : null;
		},
	
		// Dates (only those recognised by the browser's Date.parse)
		function ( d, settings )
		{
			// V8 will remove any unknown characters at the start and end of the
			// expression, leading to false matches such as `$245.12` or `10%` being
			// a valid date. See forum thread 18941 for detail.
			if ( d && !(d instanceof Date) && ( ! _re_date_start.test(d) || ! _re_date_end.test(d) ) ) {
				return null;
			}
			var parsed = Date.parse(d);
			return (parsed !== null && !isNaN(parsed)) || _empty(d) ? 'date' : null;
		},
	
		// Formatted numbers
		function ( d, settings )
		{
			var decimal = settings.oLanguage.sDecimal;
			return _isNumber( d, decimal, true ) ? 'num-fmt'+decimal : null;
		},
	
		// HTML numeric
		function ( d, settings )
		{
			var decimal = settings.oLanguage.sDecimal;
			return _htmlNumeric( d, decimal ) ? 'html-num'+decimal : null;
		},
	
		// HTML numeric, formatted
		function ( d, settings )
		{
			var decimal = settings.oLanguage.sDecimal;
			return _htmlNumeric( d, decimal, true ) ? 'html-num-fmt'+decimal : null;
		},
	
		// HTML (this is strict checking - there must be html)
		function ( d, settings )
		{
			return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1) ?
				'html' : null;
		}
	] );
	
	
	
	// Filter formatting functions. See model.ext.ofnSearch for information about
	// what is required from these methods.
	// 
	// Note that additional search methods are added for the html numbers and
	// html formatted numbers by `_addNumericSort()` when we know what the decimal
	// place is
	
	
	$.extend( DataTable.ext.type.search, {
		html: function ( data ) {
			return _empty(data) ?
				data :
				typeof data === 'string' ?
					data
						.replace( _re_new_lines, " " )
						.replace( _re_html, "" ) :
					'';
		},
	
		string: function ( data ) {
			return _empty(data) ?
				data :
				typeof data === 'string' ?
					data.replace( _re_new_lines, " " ) :
					data;
		}
	} );
	
	
	
	var __numericReplace = function ( d, decimalPlace, re1, re2 ) {
		if ( d !== 0 && (!d || d === '-') ) {
			return -Infinity;
		}
	
		// If a decimal place other than `.` is used, it needs to be given to the
		// function so we can detect it and replace with a `.` which is the only
		// decimal place Javascript recognises - it is not locale aware.
		if ( decimalPlace ) {
			d = _numToDecimal( d, decimalPlace );
		}
	
		if ( d.replace ) {
			if ( re1 ) {
				d = d.replace( re1, '' );
			}
	
			if ( re2 ) {
				d = d.replace( re2, '' );
			}
		}
	
		return d * 1;
	};
	
	
	// Add the numeric 'deformatting' functions for sorting and search. This is done
	// in a function to provide an easy ability for the language options to add
	// additional methods if a non-period decimal place is used.
	function _addNumericSort ( decimalPlace ) {
		$.each(
			{
				// Plain numbers
				"num": function ( d ) {
					return __numericReplace( d, decimalPlace );
				},
	
				// Formatted numbers
				"num-fmt": function ( d ) {
					return __numericReplace( d, decimalPlace, _re_formatted_numeric );
				},
	
				// HTML numeric
				"html-num": function ( d ) {
					return __numericReplace( d, decimalPlace, _re_html );
				},
	
				// HTML numeric, formatted
				"html-num-fmt": function ( d ) {
					return __numericReplace( d, decimalPlace, _re_html, _re_formatted_numeric );
				}
			},
			function ( key, fn ) {
				// Add the ordering method
				_ext.type.order[ key+decimalPlace+'-pre' ] = fn;
	
				// For HTML types add a search formatter that will strip the HTML
				if ( key.match(/^html\-/) ) {
					_ext.type.search[ key+decimalPlace ] = _ext.type.search.html;
				}
			}
		);
	}
	
	
	// Default sort methods
	$.extend( _ext.type.order, {
		// Dates
		"date-pre": function ( d ) {
			return Date.parse( d ) || 0;
		},
	
		// html
		"html-pre": function ( a ) {
			return _empty(a) ?
				'' :
				a.replace ?
					a.replace( /<.*?>/g, "" ).toLowerCase() :
					a+'';
		},
	
		// string
		"string-pre": function ( a ) {
			// This is a little complex, but faster than always calling toString,
			// http://jsperf.com/tostring-v-check
			return _empty(a) ?
				'' :
				typeof a === 'string' ?
					a.toLowerCase() :
					! a.toString ?
						'' :
						a.toString();
		},
	
		// string-asc and -desc are retained only for compatibility with the old
		// sort methods
		"string-asc": function ( x, y ) {
			return ((x < y) ? -1 : ((x > y) ? 1 : 0));
		},
	
		"string-desc": function ( x, y ) {
			return ((x < y) ? 1 : ((x > y) ? -1 : 0));
		}
	} );
	
	
	// Numeric sorting types - order doesn't matter here
	_addNumericSort( '' );
	
	
	$.extend( true, DataTable.ext.renderer, {
		header: {
			_: function ( settings, cell, column, classes ) {
				// No additional mark-up required
				// Attach a sort listener to update on sort - note that using the
				// `DT` namespace will allow the event to be removed automatically
				// on destroy, while the `dt` namespaced event is the one we are
				// listening for
				$(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) {
					if ( settings !== ctx ) { // need to check this this is the host
						return;               // table, not a nested one
					}
	
					var colIdx = column.idx;
	
					cell
						.removeClass(
							column.sSortingClass +' '+
							classes.sSortAsc +' '+
							classes.sSortDesc
						)
						.addClass( columns[ colIdx ] == 'asc' ?
							classes.sSortAsc : columns[ colIdx ] == 'desc' ?
								classes.sSortDesc :
								column.sSortingClass
						);
				} );
			},
	
			jqueryui: function ( settings, cell, column, classes ) {
				$('<div/>')
					.addClass( classes.sSortJUIWrapper )
					.append( cell.contents() )
					.append( $('<span/>')
						.addClass( classes.sSortIcon+' '+column.sSortingClassJUI )
					)
					.appendTo( cell );
	
				// Attach a sort listener to update on sort
				$(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) {
					if ( settings !== ctx ) {
						return;
					}
	
					var colIdx = column.idx;
	
					cell
						.removeClass( classes.sSortAsc +" "+classes.sSortDesc )
						.addClass( columns[ colIdx ] == 'asc' ?
							classes.sSortAsc : columns[ colIdx ] == 'desc' ?
								classes.sSortDesc :
								column.sSortingClass
						);
	
					cell
						.find( 'span.'+classes.sSortIcon )
						.removeClass(
							classes.sSortJUIAsc +" "+
							classes.sSortJUIDesc +" "+
							classes.sSortJUI +" "+
							classes.sSortJUIAscAllowed +" "+
							classes.sSortJUIDescAllowed
						)
						.addClass( columns[ colIdx ] == 'asc' ?
							classes.sSortJUIAsc : columns[ colIdx ] == 'desc' ?
								classes.sSortJUIDesc :
								column.sSortingClassJUI
						);
				} );
			}
		}
	} );
	
	/*
	 * Public helper functions. These aren't used internally by DataTables, or
	 * called by any of the options passed into DataTables, but they can be used
	 * externally by developers working with DataTables. They are helper functions
	 * to make working with DataTables a little bit easier.
	 */
	
	var __htmlEscapeEntities = function ( d ) {
		return typeof d === 'string' ?
			d.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;') :
			d;
	};
	
	/**
	 * Helpers for `columns.render`.
	 *
	 * The options defined here can be used with the `columns.render` initialisation
	 * option to provide a display renderer. The following functions are defined:
	 *
	 * * `number` - Will format numeric data (defined by `columns.data`) for
	 *   display, retaining the original unformatted data for sorting and filtering.
	 *   It takes 5 parameters:
	 *   * `string` - Thousands grouping separator
	 *   * `string` - Decimal point indicator
	 *   * `integer` - Number of decimal points to show
	 *   * `string` (optional) - Prefix.
	 *   * `string` (optional) - Postfix (/suffix).
	 * * `text` - Escape HTML to help prevent XSS attacks. It has no optional
	 *   parameters.
	 *
	 * @example
	 *   // Column definition using the number renderer
	 *   {
	 *     data: "salary",
	 *     render: $.fn.dataTable.render.number( '\'', '.', 0, '$' )
	 *   }
	 *
	 * @namespace
	 */
	DataTable.render = {
		number: function ( thousands, decimal, precision, prefix, postfix ) {
			return {
				display: function ( d ) {
					if ( typeof d !== 'number' && typeof d !== 'string' ) {
						return d;
					}
	
					var negative = d < 0 ? '-' : '';
					var flo = parseFloat( d );
	
					// If NaN then there isn't much formatting that we can do - just
					// return immediately, escaping any HTML (this was supposed to
					// be a number after all)
					if ( isNaN( flo ) ) {
						return __htmlEscapeEntities( d );
					}
	
					d = Math.abs( flo );
	
					var intPart = parseInt( d, 10 );
					var floatPart = precision ?
						decimal+(d - intPart).toFixed( precision ).substring( 2 ):
						'';
	
					return negative + (prefix||'') +
						intPart.toString().replace(
							/\B(?=(\d{3})+(?!\d))/g, thousands
						) +
						floatPart +
						(postfix||'');
				}
			};
		},
	
		text: function () {
			return {
				display: __htmlEscapeEntities
			};
		}
	};
	
	
	/*
	 * This is really a good bit rubbish this method of exposing the internal methods
	 * publicly... - To be fixed in 2.0 using methods on the prototype
	 */
	
	
	/**
	 * Create a wrapper function for exporting an internal functions to an external API.
	 *  @param {string} fn API function name
	 *  @returns {function} wrapped function
	 *  @memberof DataTable#internal
	 */
	function _fnExternApiFunc (fn)
	{
		return function() {
			var args = [_fnSettingsFromNode( this[DataTable.ext.iApiIndex] )].concat(
				Array.prototype.slice.call(arguments)
			);
			return DataTable.ext.internal[fn].apply( this, args );
		};
	}
	
	
	/**
	 * Reference to internal functions for use by plug-in developers. Note that
	 * these methods are references to internal functions and are considered to be
	 * private. If you use these methods, be aware that they are liable to change
	 * between versions.
	 *  @namespace
	 */
	$.extend( DataTable.ext.internal, {
		_fnExternApiFunc: _fnExternApiFunc,
		_fnBuildAjax: _fnBuildAjax,
		_fnAjaxUpdate: _fnAjaxUpdate,
		_fnAjaxParameters: _fnAjaxParameters,
		_fnAjaxUpdateDraw: _fnAjaxUpdateDraw,
		_fnAjaxDataSrc: _fnAjaxDataSrc,
		_fnAddColumn: _fnAddColumn,
		_fnColumnOptions: _fnColumnOptions,
		_fnAdjustColumnSizing: _fnAdjustColumnSizing,
		_fnVisibleToColumnIndex: _fnVisibleToColumnIndex,
		_fnColumnIndexToVisible: _fnColumnIndexToVisible,
		_fnVisbleColumns: _fnVisbleColumns,
		_fnGetColumns: _fnGetColumns,
		_fnColumnTypes: _fnColumnTypes,
		_fnApplyColumnDefs: _fnApplyColumnDefs,
		_fnHungarianMap: _fnHungarianMap,
		_fnCamelToHungarian: _fnCamelToHungarian,
		_fnLanguageCompat: _fnLanguageCompat,
		_fnBrowserDetect: _fnBrowserDetect,
		_fnAddData: _fnAddData,
		_fnAddTr: _fnAddTr,
		_fnNodeToDataIndex: _fnNodeToDataIndex,
		_fnNodeToColumnIndex: _fnNodeToColumnIndex,
		_fnGetCellData: _fnGetCellData,
		_fnSetCellData: _fnSetCellData,
		_fnSplitObjNotation: _fnSplitObjNotation,
		_fnGetObjectDataFn: _fnGetObjectDataFn,
		_fnSetObjectDataFn: _fnSetObjectDataFn,
		_fnGetDataMaster: _fnGetDataMaster,
		_fnClearTable: _fnClearTable,
		_fnDeleteIndex: _fnDeleteIndex,
		_fnInvalidate: _fnInvalidate,
		_fnGetRowElements: _fnGetRowElements,
		_fnCreateTr: _fnCreateTr,
		_fnBuildHead: _fnBuildHead,
		_fnDrawHead: _fnDrawHead,
		_fnDraw: _fnDraw,
		_fnReDraw: _fnReDraw,
		_fnAddOptionsHtml: _fnAddOptionsHtml,
		_fnDetectHeader: _fnDetectHeader,
		_fnGetUniqueThs: _fnGetUniqueThs,
		_fnFeatureHtmlFilter: _fnFeatureHtmlFilter,
		_fnFilterComplete: _fnFilterComplete,
		_fnFilterCustom: _fnFilterCustom,
		_fnFilterColumn: _fnFilterColumn,
		_fnFilter: _fnFilter,
		_fnFilterCreateSearch: _fnFilterCreateSearch,
		_fnEscapeRegex: _fnEscapeRegex,
		_fnFilterData: _fnFilterData,
		_fnFeatureHtmlInfo: _fnFeatureHtmlInfo,
		_fnUpdateInfo: _fnUpdateInfo,
		_fnInfoMacros: _fnInfoMacros,
		_fnInitialise: _fnInitialise,
		_fnInitComplete: _fnInitComplete,
		_fnLengthChange: _fnLengthChange,
		_fnFeatureHtmlLength: _fnFeatureHtmlLength,
		_fnFeatureHtmlPaginate: _fnFeatureHtmlPaginate,
		_fnPageChange: _fnPageChange,
		_fnFeatureHtmlProcessing: _fnFeatureHtmlProcessing,
		_fnProcessingDisplay: _fnProcessingDisplay,
		_fnFeatureHtmlTable: _fnFeatureHtmlTable,
		_fnScrollDraw: _fnScrollDraw,
		_fnApplyToChildren: _fnApplyToChildren,
		_fnCalculateColumnWidths: _fnCalculateColumnWidths,
		_fnThrottle: _fnThrottle,
		_fnConvertToWidth: _fnConvertToWidth,
		_fnGetWidestNode: _fnGetWidestNode,
		_fnGetMaxLenString: _fnGetMaxLenString,
		_fnStringToCss: _fnStringToCss,
		_fnSortFlatten: _fnSortFlatten,
		_fnSort: _fnSort,
		_fnSortAria: _fnSortAria,
		_fnSortListener: _fnSortListener,
		_fnSortAttachListener: _fnSortAttachListener,
		_fnSortingClasses: _fnSortingClasses,
		_fnSortData: _fnSortData,
		_fnSaveState: _fnSaveState,
		_fnLoadState: _fnLoadState,
		_fnSettingsFromNode: _fnSettingsFromNode,
		_fnLog: _fnLog,
		_fnMap: _fnMap,
		_fnBindAction: _fnBindAction,
		_fnCallbackReg: _fnCallbackReg,
		_fnCallbackFire: _fnCallbackFire,
		_fnLengthOverflow: _fnLengthOverflow,
		_fnRenderer: _fnRenderer,
		_fnDataSource: _fnDataSource,
		_fnRowAttributes: _fnRowAttributes,
		_fnCalculateEnd: function () {} // Used by a lot of plug-ins, but redundant
		                                // in 1.10, so this dead-end function is
		                                // added to prevent errors
	} );
	

	// jQuery access
	$.fn.dataTable = DataTable;

	// Provide access to the host jQuery object (circular reference)
	DataTable.$ = $;

	// Legacy aliases
	$.fn.dataTableSettings = DataTable.settings;
	$.fn.dataTableExt = DataTable.ext;

	// With a capital `D` we return a DataTables API instance rather than a
	// jQuery object
	$.fn.DataTable = function ( opts ) {
		return $(this).dataTable( opts ).api();
	};

	// All properties that are available to $.fn.dataTable should also be
	// available on $.fn.DataTable
	$.each( DataTable, function ( prop, val ) {
		$.fn.DataTable[ prop ] = val;
	} );


	// Information about events fired by DataTables - for documentation.
	/**
	 * Draw event, fired whenever the table is redrawn on the page, at the same
	 * point as fnDrawCallback. This may be useful for binding events or
	 * performing calculations when the table is altered at all.
	 *  @name DataTable#draw.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 */

	/**
	 * Search event, fired when the searching applied to the table (using the
	 * built-in global search, or column filters) is altered.
	 *  @name DataTable#search.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 */

	/**
	 * Page change event, fired when the paging of the table is altered.
	 *  @name DataTable#page.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 */

	/**
	 * Order event, fired when the ordering applied to the table is altered.
	 *  @name DataTable#order.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 */

	/**
	 * DataTables initialisation complete event, fired when the table is fully
	 * drawn, including Ajax data loaded, if Ajax data is required.
	 *  @name DataTable#init.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} oSettings DataTables settings object
	 *  @param {object} json The JSON object request from the server - only
	 *    present if client-side Ajax sourced data is used</li></ol>
	 */

	/**
	 * State save event, fired when the table has changed state a new state save
	 * is required. This event allows modification of the state saving object
	 * prior to actually doing the save, including addition or other state
	 * properties (for plug-ins) or modification of a DataTables core property.
	 *  @name DataTable#stateSaveParams.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} oSettings DataTables settings object
	 *  @param {object} json The state information to be saved
	 */

	/**
	 * State load event, fired when the table is loading state from the stored
	 * data, but prior to the settings object being modified by the saved state
	 * - allowing modification of the saved state is required or loading of
	 * state for a plug-in.
	 *  @name DataTable#stateLoadParams.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} oSettings DataTables settings object
	 *  @param {object} json The saved state information
	 */

	/**
	 * State loaded event, fired when state has been loaded from stored data and
	 * the settings object has been modified by the loaded data.
	 *  @name DataTable#stateLoaded.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} oSettings DataTables settings object
	 *  @param {object} json The saved state information
	 */

	/**
	 * Processing event, fired when DataTables is doing some kind of processing
	 * (be it, order, searcg or anything else). It can be used to indicate to
	 * the end user that there is something happening, or that something has
	 * finished.
	 *  @name DataTable#processing.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} oSettings DataTables settings object
	 *  @param {boolean} bShow Flag for if DataTables is doing processing or not
	 */

	/**
	 * Ajax (XHR) event, fired whenever an Ajax request is completed from a
	 * request to made to the server for new data. This event is called before
	 * DataTables processed the returned data, so it can also be used to pre-
	 * process the data returned from the server, if needed.
	 *
	 * Note that this trigger is called in `fnServerData`, if you override
	 * `fnServerData` and which to use this event, you need to trigger it in you
	 * success function.
	 *  @name DataTable#xhr.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 *  @param {object} json JSON returned from the server
	 *
	 *  @example
	 *     // Use a custom property returned from the server in another DOM element
	 *     $('#table').dataTable().on('xhr.dt', function (e, settings, json) {
	 *       $('#status').html( json.status );
	 *     } );
	 *
	 *  @example
	 *     // Pre-process the data returned from the server
	 *     $('#table').dataTable().on('xhr.dt', function (e, settings, json) {
	 *       for ( var i=0, ien=json.aaData.length ; i<ien ; i++ ) {
	 *         json.aaData[i].sum = json.aaData[i].one + json.aaData[i].two;
	 *       }
	 *       // Note no return - manipulate the data directly in the JSON object.
	 *     } );
	 */

	/**
	 * Destroy event, fired when the DataTable is destroyed by calling fnDestroy
	 * or passing the bDestroy:true parameter in the initialisation object. This
	 * can be used to remove bound events, added DOM nodes, etc.
	 *  @name DataTable#destroy.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 */

	/**
	 * Page length change event, fired when number of records to show on each
	 * page (the length) is changed.
	 *  @name DataTable#length.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 *  @param {integer} len New length
	 */

	/**
	 * Column sizing has changed.
	 *  @name DataTable#column-sizing.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 */

	/**
	 * Column visibility has changed.
	 *  @name DataTable#column-visibility.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 *  @param {int} column Column index
	 *  @param {bool} vis `false` if column now hidden, or `true` if visible
	 */

	return $.fn.dataTable;
}));


/*! AutoFill 2.1.2
 * ©2008-2015 SpryMedia Ltd - datatables.net/license
 */

/**
 * @summary     AutoFill
 * @description Add Excel like click and drag auto-fill options to DataTables
 * @version     2.1.2
 * @file        dataTables.autoFill.js
 * @author      SpryMedia Ltd (www.sprymedia.co.uk)
 * @contact     www.sprymedia.co.uk/contact
 * @copyright   Copyright 2010-2015 SpryMedia Ltd.
 *
 * This source file is free software, available under the following license:
 *   MIT license - http://datatables.net/license/mit
 *
 * This source file is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
 *
 * For details please refer to: http://www.datatables.net
 */
(function( factory ){
	if ( typeof define === 'function' && define.amd ) {
		// AMD
		define( ['jquery', 'datatables.net'], function ( $ ) {
			return factory( $, window, document );
		} );
	}
	else if ( typeof exports === 'object' ) {
		// CommonJS
		module.exports = function (root, $) {
			if ( ! root ) {
				root = window;
			}

			if ( ! $ || ! $.fn.dataTable ) {
				$ = require('datatables.net')(root, $).$;
			}

			return factory( $, root, root.document );
		};
	}
	else {
		// Browser
		factory( jQuery, window, document );
	}
}(function( $, window, document, undefined ) {
'use strict';
var DataTable = $.fn.dataTable;


var _instance = 0;

/** 
 * AutoFill provides Excel like auto-fill features for a DataTable
 *
 * @class AutoFill
 * @constructor
 * @param {object} oTD DataTables settings object
 * @param {object} oConfig Configuration object for AutoFill
 */
var AutoFill = function( dt, opts )
{
	if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.8' ) ) {
		throw( "Warning: AutoFill requires DataTables 1.10.8 or greater");
	}

	// User and defaults configuration object
	this.c = $.extend( true, {},
		DataTable.defaults.autoFill,
		AutoFill.defaults,
		opts
	);

	/**
	 * @namespace Settings object which contains customisable information for AutoFill instance
	 */
	this.s = {
		/** @type {DataTable.Api} DataTables' API instance */
		dt: new DataTable.Api( dt ),

		/** @type {String} Unique namespace for events attached to the document */
		namespace: '.autoFill'+(_instance++),

		/** @type {Object} Cached dimension information for use in the mouse move event handler */
		scroll: {},

		/** @type {integer} Interval object used for smooth scrolling */
		scrollInterval: null,

		handle: {
			height: 0,
			width: 0
		}
	};


	/**
	 * @namespace Common and useful DOM elements for the class instance
	 */
	this.dom = {
		/** @type {jQuery} AutoFill handle */
		handle: $('<div class="dt-autofill-handle"/>'),

		/**
		 * @type {Object} Selected cells outline - Need to use 4 elements,
		 *   otherwise the mouse over if you back into the selected rectangle
		 *   will be over that element, rather than the cells!
		 */
		select: {
			top:    $('<div class="dt-autofill-select top"/>'),
			right:  $('<div class="dt-autofill-select right"/>'),
			bottom: $('<div class="dt-autofill-select bottom"/>'),
			left:   $('<div class="dt-autofill-select left"/>')
		},

		/** @type {jQuery} Fill type chooser background */
		background: $('<div class="dt-autofill-background"/>'),

		/** @type {jQuery} Fill type chooser */
		list: $('<div class="dt-autofill-list">'+this.s.dt.i18n('autoFill.info', '')+'<ul/></div>'),

		/** @type {jQuery} DataTables scrolling container */
		dtScroll: null,

		/** @type {jQuery} Offset parent element */
		offsetParent: null
	};


	/* Constructor logic */
	this._constructor();
};



$.extend( AutoFill.prototype, {
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Constructor
	 */

	/**
	 * Initialise the RowReorder instance
	 *
	 * @private
	 */
	_constructor: function ()
	{
		var that = this;
		var dt = this.s.dt;
		var dtScroll = $('div.dataTables_scrollBody', this.s.dt.table().container());

		if ( dtScroll.length ) {
			this.dom.dtScroll = dtScroll;

			// Need to scroll container to be the offset parent
			if ( dtScroll.css('position') === 'static' ) {
				dtScroll.css( 'position', 'relative' );
			}
		}

		this._focusListener();

		this.dom.handle.on( 'mousedown', function (e) {
			that._mousedown( e );
			return false;
		} );

		dt.on( 'destroy.autoFill', function () {
			dt.off( '.autoFill' );
			$(dt.table().body()).off( that.s.namespace );
			$(document.body).off( that.s.namespace );
		} );
	},


	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Private methods
	 */

	/**
	 * Display the AutoFill drag handle by appending it to a table cell. This
	 * is the opposite of the _detach method.
	 *
	 * @param  {node} node TD/TH cell to insert the handle into
	 * @private
	 */
	_attach: function ( node )
	{
		var dt = this.s.dt;
		var idx = dt.cell( node ).index();
		var handle = this.dom.handle;
		var handleDim = this.s.handle;
		var dtScroll = $('div.dataTables_scrollBody', this.s.dt.table().container() );
		var scrollOffsetX=0, scrollOffsetY=0;

		if ( ! idx || dt.columns( this.c.columns ).indexes().indexOf( idx.column ) === -1 ) {
			this._detach();
			return;
		}

		if ( ! this.dom.offsetParent ) {
			this.dom.offsetParent = $(node).offsetParent();
		}

		if ( ! handleDim.height || ! handleDim.width ) {
			// Append to document so we can get its size. Not expecting it to
			// change during the life time of the page
			handle.appendTo( 'body' );
			handleDim.height = handle.outerHeight();
			handleDim.width = handle.outerWidth();
		}

		var offset = $(node).position();

		// If scrolling, and the table is not itself the offset parent, need to
		// offset for the scrolling position
		if ( dtScroll.length && this.dom.offsetParent[0] !== dt.table().node() ) {
			scrollOffsetY = dtScroll.scrollTop();
			scrollOffsetX = dtScroll.scrollLeft();
		}

		this.dom.attachedTo = node;
		handle
			.css( {
				top: offset.top + node.offsetHeight - handleDim.height + scrollOffsetY,
				left: offset.left + node.offsetWidth - handleDim.width + scrollOffsetX
			} )
			.appendTo( this.dom.offsetParent );
	},


	/**
	 * Determine can the fill type should be. This can be automatic, or ask the
	 * end user.
	 *
	 * @param {array} cells Information about the selected cells from the key
	 *     up function
	 * @private
	 */
	_actionSelector: function ( cells )
	{
		var that = this;
		var dt = this.s.dt;
		var actions = AutoFill.actions;
		var available = [];

		// "Ask" each plug-in if it wants to handle this data
		$.each( actions, function ( key, action ) {
			if ( action.available( dt, cells ) ) {
				available.push( key );
			}
		} );

		if ( available.length === 1 && this.c.alwaysAsk === false ) {
			// Only one action available - enact it immediately
			var result = actions[ available[0] ].execute( dt, cells );
			this._update( result, cells );
		}
		else {
			// Multiple actions available - ask the end user what they want to do
			var list = this.dom.list.children('ul').empty();

			// Add a cancel option
			available.push( 'cancel' );

			$.each( available, function ( i, name ) {
				list.append( $('<li/>')
					.append(
						'<div class="dt-autofill-question">'+
							actions[ name ].option( dt, cells )+
						'<div>'
					)
					.append( $('<div class="dt-autofill-button">' )
						.append( $('<button class="'+AutoFill.classes.btn+'">'+dt.i18n('autoFill.button', '&gt;')+'</button>')
							.on( 'click', function () {
								var result = actions[ name ].execute(
									dt, cells, $(this).closest('li')
								);
								that._update( result, cells );

								that.dom.background.remove();
								that.dom.list.remove();
							} )
						)
					)
				);
			} );

			this.dom.background.appendTo( 'body' );
			this.dom.list.appendTo( 'body' );

			this.dom.list.css( 'margin-top', this.dom.list.outerHeight()/2 * -1 );
		}
	},


	/**
	 * Remove the AutoFill handle from the document
	 *
	 * @private
	 */
	_detach: function ()
	{
		this.dom.attachedTo = null;
		this.dom.handle.detach();
	},


	/**
	 * Draw the selection outline by calculating the range between the start
	 * and end cells, then placing the highlighting elements to draw a rectangle
	 *
	 * @param  {node}   target End cell
	 * @param  {object} e      Originating event
	 * @private
	 */
	_drawSelection: function ( target, e )
	{
		// Calculate boundary for start cell to this one
		var dt = this.s.dt;
		var start = this.s.start;
		var startCell = $(this.dom.start);
		var endCell = $(target);
		var end = {
			row: dt.rows( { page: 'current' } ).nodes().indexOf( endCell.parent()[0] ),
			column: endCell.index()
		};

		// Be sure that is a DataTables controlled cell
		if ( ! dt.cell( endCell ).any() ) {
			return;
		}

		// if target is not in the columns available - do nothing
		if ( dt.columns( this.c.columns ).indexes().indexOf( end.column ) === -1 ) {
			return;
		}

		this.s.end = end;

		var top, bottom, left, right, height, width;

		top    = start.row    < end.row    ? startCell : endCell;
		bottom = start.row    < end.row    ? endCell   : startCell;
		left   = start.column < end.column ? startCell : endCell;
		right  = start.column < end.column ? endCell   : startCell;

		top    = top.position().top;
		left   = left.position().left;
		height = bottom.position().top + bottom.outerHeight() - top;
		width  = right.position().left + right.outerWidth() - left;

		var dtScroll = this.dom.dtScroll;
		if ( dtScroll && this.dom.offsetParent[0] !== dt.table().node() ) {
			top += dtScroll.scrollTop();
			left += dtScroll.scrollLeft();
		}

		var select = this.dom.select;
		select.top.css( {
			top: top,
			left: left,
			width: width
		} );

		select.left.css( {
			top: top,
			left: left,
			height: height
		} );

		select.bottom.css( {
			top: top + height,
			left: left,
			width: width
		} );

		select.right.css( {
			top: top,
			left: left + width,
			height: height
		} );
	},


	/**
	 * Use the Editor API to perform an update based on the new data for the
	 * cells
	 *
	 * @param {array} cells Information about the selected cells from the key
	 *     up function
	 * @private
	 */
	_editor: function ( cells )
	{
		var dt = this.s.dt;
		var editor = this.c.editor;

		if ( ! editor ) {
			return;
		}

		// Build the object structure for Editor's multi-row editing
		var idValues = {};
		var nodes = [];
		var fields = editor.fields();

		for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
			for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
				var cell = cells[i][j];

				// Determine the field name for the cell being edited
				var col = dt.settings()[0].aoColumns[ cell.index.column ];
				var fieldName = col.editField;

				if ( fieldName === undefined ) {
					var dataSrc = col.mData;

					// dataSrc is the `field.data` property, but we need to set
					// using the field name, so we need to translate from the
					// data to the name
					for ( var k=0, ken=fields.length ; k<ken ; k++ ) {
						var field = editor.field( fields[k] );

						if ( field.dataSrc() === dataSrc ) {
							fieldName = field.name();
							break;
						}
					}
				}

				if ( ! fieldName ) {
					throw 'Could not automatically determine field data. '+
						'Please see https://datatables.net/tn/11';
				}

				if ( ! idValues[ fieldName ] ) {
					idValues[ fieldName ] = {};
				}

				var id = dt.row( cell.index.row ).id();
				idValues[ fieldName ][ id ] = cell.set;

				// Keep a list of cells so we can activate the bubble editing
				// with them
				nodes.push( cell.index );
			}
		}

		// Perform the edit using bubble editing as it allows us to specify
		// the cells to be edited, rather than using full rows
		editor
			.bubble( nodes, false )
			.multiSet( idValues )
			.submit();
	},


	/**
	 * Emit an event on the DataTable for listeners
	 *
	 * @param  {string} name Event name
	 * @param  {array} args Event arguments
	 * @private
	 */
	_emitEvent: function ( name, args )
	{
		this.s.dt.iterator( 'table', function ( ctx, i ) {
			$(ctx.nTable).triggerHandler( name+'.dt', args );
		} );
	},


	/**
	 * Attach suitable listeners (based on the configuration) that will attach
	 * and detach the AutoFill handle in the document.
	 *
	 * @private
	 */
	_focusListener: function ()
	{
		var that = this;
		var dt = this.s.dt;
		var namespace = this.s.namespace;
		var focus = this.c.focus !== null ?
			this.c.focus :
			dt.settings()[0].keytable ?
				'focus' :
				'hover';

		// All event listeners attached here are removed in the `destroy`
		// callback in the constructor
		if ( focus === 'focus' ) {
			dt
				.on( 'key-focus.autoFill', function ( e, dt, cell ) {
					that._attach( cell.node() );
				} )
				.on( 'key-blur.autoFill', function ( e, dt, cell ) {
					that._detach();
				} );
		}
		else if ( focus === 'click' ) {
			$(dt.table().body()).on( 'click'+namespace, 'td, th', function (e) {
				that._attach( this );
			} );

			$(document.body).on( 'click'+namespace, function (e) {
				if ( ! $(e.target).parents().filter( dt.table().body() ).length ) {
					that._detach();
				}
			} );
		}
		else {
			$(dt.table().body())
				.on( 'mouseenter'+namespace, 'td, th', function (e) {
					that._attach( this );
				} )
				.on( 'mouseleave'+namespace, function (e) {
					if ( $(e.relatedTarget).hasClass('dt-autofill-handle') ) {
						return;
					}

					that._detach();
				} );
		}
	},


	/**
	 * Start mouse drag - selects the start cell
	 *
	 * @param  {object} e Mouse down event
	 * @private
	 */
	_mousedown: function ( e )
	{
		var that = this;
		var dt = this.s.dt;

		this.dom.start = this.dom.attachedTo;
		this.s.start = {
			row: dt.rows( { page: 'current' } ).nodes().indexOf( $(this.dom.start).parent()[0] ),
			column: $(this.dom.start).index()
		};

		$(document.body)
			.on( 'mousemove.autoFill', function (e) {
				that._mousemove( e );
			} )
			.on( 'mouseup.autoFill', function (e) {
				that._mouseup( e );
			} );

		var select = this.dom.select;
		var offsetParent = $(this.s.dt.table().body()).offsetParent();
		select.top.appendTo( offsetParent );
		select.left.appendTo( offsetParent );
		select.right.appendTo( offsetParent );
		select.bottom.appendTo( offsetParent );

		this._drawSelection( this.dom.start, e );

		this.dom.handle.css( 'display', 'none' );

		// Cache scrolling information so mouse move doesn't need to read.
		// This assumes that the window and DT scroller will not change size
		// during an AutoFill drag, which I think is a fair assumption
		var scrollWrapper = this.dom.dtScroll;
		this.s.scroll = {
			windowHeight: $(window).height(),
			windowWidth:  $(window).width(),
			dtTop:        scrollWrapper ? scrollWrapper.offset().top : null,
			dtLeft:       scrollWrapper ? scrollWrapper.offset().left : null,
			dtHeight:     scrollWrapper ? scrollWrapper.outerHeight() : null,
			dtWidth:      scrollWrapper ? scrollWrapper.outerWidth() : null
		};
	},


	/**
	 * Mouse drag - selects the end cell and update the selection display for
	 * the end user
	 *
	 * @param  {object} e Mouse move event
	 * @private
	 */
	_mousemove: function ( e )
	{	
		var that = this;
		var dt = this.s.dt;
		var name = e.target.nodeName.toLowerCase();
		if ( name !== 'td' && name !== 'th' ) {
			return;
		}

		this._drawSelection( e.target, e );
		this._shiftScroll( e );
	},


	/**
	 * End mouse drag - perform the update actions
	 *
	 * @param  {object} e Mouse up event
	 * @private
	 */
	_mouseup: function ( e )
	{
		$(document.body).off( '.autoFill' );

		var dt = this.s.dt;
		var select = this.dom.select;
		select.top.remove();
		select.left.remove();
		select.right.remove();
		select.bottom.remove();

		this.dom.handle.css( 'display', 'block' );

		// Display complete - now do something useful with the selection!
		var start = this.s.start;
		var end = this.s.end;

		// Haven't selected multiple cells, so nothing to do
		if ( start.row === end.row && start.column === end.column ) {
			return;
		}

		// Build a matrix representation of the selected rows
		var rows       = this._range( start.row, end.row );
		var columns    = this._range( start.column, end.column );
		var selected   = [];
		var dtSettings = dt.settings()[0];
		var dtColumns  = dtSettings.aoColumns;

		// Can't use Array.prototype.map as IE8 doesn't support it
		// Can't use $.map as jQuery flattens 2D arrays
		// Need to use a good old fashioned for loop
		for ( var rowIdx=0 ; rowIdx<rows.length ; rowIdx++ ) {
			selected.push(
				$.map( columns, function (column) {
					var cell = dt.cell( ':eq('+rows[rowIdx]+')', column+':visible', {page:'current'} );
					var data = cell.data();
					var cellIndex = cell.index();
					var editField = dtColumns[ cellIndex.column ].editField;

					if ( editField !== undefined ) {
						data = dtSettings.oApi._fnGetObjectDataFn( editField )( dt.row( cellIndex.row ).data() );
					}

					return {
						cell:  cell,
						data:  data,
						label: cell.data(),
						index: cellIndex
					};
				} )
			);
		}

		this._actionSelector( selected );
		
		// Stop shiftScroll
		clearInterval( this.s.scrollInterval );
		this.s.scrollInterval = null;
	},


	/**
	 * Create an array with a range of numbers defined by the start and end
	 * parameters passed in (inclusive!).
	 * 
	 * @param  {integer} start Start
	 * @param  {integer} end   End
	 * @private
	 */
	_range: function ( start, end )
	{
		var out = [];
		var i;

		if ( start <= end ) {
			for ( i=start ; i<=end ; i++ ) {
				out.push( i );
			}
		}
		else {
			for ( i=start ; i>=end ; i-- ) {
				out.push( i );
			}
		}

		return out;
	},


	/**
	 * Move the window and DataTables scrolling during a drag to scroll new
	 * content into view. This is done by proximity to the edge of the scrolling
	 * container of the mouse - for example near the top edge of the window
	 * should scroll up. This is a little complicated as there are two elements
	 * that can be scrolled - the window and the DataTables scrolling view port
	 * (if scrollX and / or scrollY is enabled).
	 *
	 * @param  {object} e Mouse move event object
	 * @private
	 */
	_shiftScroll: function ( e )
	{
		var that = this;
		var dt = this.s.dt;
		var scroll = this.s.scroll;
		var runInterval = false;
		var scrollSpeed = 5;
		var buffer = 65;
		var
			windowY = e.pageY - document.body.scrollTop,
			windowX = e.pageX - document.body.scrollLeft,
			windowVert, windowHoriz,
			dtVert, dtHoriz;

		// Window calculations - based on the mouse position in the window,
		// regardless of scrolling
		if ( windowY < buffer ) {
			windowVert = scrollSpeed * -1;
		}
		else if ( windowY > scroll.windowHeight - buffer ) {
			windowVert = scrollSpeed;
		}

		if ( windowX < buffer ) {
			windowHoriz = scrollSpeed * -1;
		}
		else if ( windowX > scroll.windowWidth - buffer ) {
			windowHoriz = scrollSpeed;
		}

		// DataTables scrolling calculations - based on the table's position in
		// the document and the mouse position on the page
		if ( scroll.dtTop !== null && e.pageY < scroll.dtTop + buffer ) {
			dtVert = scrollSpeed * -1;
		}
		else if ( scroll.dtTop !== null && e.pageY > scroll.dtTop + scroll.dtHeight - buffer ) {
			dtVert = scrollSpeed;
		}

		if ( scroll.dtLeft !== null && e.pageX < scroll.dtLeft + buffer ) {
			dtHoriz = scrollSpeed * -1;
		}
		else if ( scroll.dtLeft !== null && e.pageX > scroll.dtLeft + scroll.dtWidth - buffer ) {
			dtHoriz = scrollSpeed;
		}

		// This is where it gets interesting. We want to continue scrolling
		// without requiring a mouse move, so we need an interval to be
		// triggered. The interval should continue until it is no longer needed,
		// but it must also use the latest scroll commands (for example consider
		// that the mouse might move from scrolling up to scrolling left, all
		// with the same interval running. We use the `scroll` object to "pass"
		// this information to the interval. Can't use local variables as they
		// wouldn't be the ones that are used by an already existing interval!
		if ( windowVert || windowHoriz || dtVert || dtHoriz ) {
			scroll.windowVert = windowVert;
			scroll.windowHoriz = windowHoriz;
			scroll.dtVert = dtVert;
			scroll.dtHoriz = dtHoriz;
			runInterval = true;
		}
		else if ( this.s.scrollInterval ) {
			// Don't need to scroll - remove any existing timer
			clearInterval( this.s.scrollInterval );
			this.s.scrollInterval = null;
		}

		// If we need to run the interval to scroll and there is no existing
		// interval (if there is an existing one, it will continue to run)
		if ( ! this.s.scrollInterval && runInterval ) {
			this.s.scrollInterval = setInterval( function () {
				// Don't need to worry about setting scroll <0 or beyond the
				// scroll bound as the browser will just reject that.
				if ( scroll.windowVert ) {
					document.body.scrollTop += scroll.windowVert;
				}
				if ( scroll.windowHoriz ) {
					document.body.scrollLeft += scroll.windowHoriz;
				}

				// DataTables scrolling
				if ( scroll.dtVert || scroll.dtHoriz ) {
					var scroller = that.dom.dtScroll[0];

					if ( scroll.dtVert ) {
						scroller.scrollTop += scroll.dtVert;
					}
					if ( scroll.dtHoriz ) {
						scroller.scrollLeft += scroll.dtHoriz;
					}
				}
			}, 20 );
		}
	},


	/**
	 * Update the DataTable after the user has selected what they want to do
	 *
	 * @param  {false|undefined} result Return from the `execute` method - can
	 *   be false internally to do nothing. This is not documented for plug-ins
	 *   and is used only by the cancel option.
	 * @param {array} cells Information about the selected cells from the key
	 *     up function, argumented with the set values
	 * @private
	 */
	_update: function ( result, cells )
	{
		// Do nothing on `false` return from an execute function
		if ( result === false ) {
			return;
		}

		var dt = this.s.dt;
		var cell;

		// Potentially allow modifications to the cells matrix
		this._emitEvent( 'preAutoFill', [ dt, cells ] );

		this._editor( cells );

		// Automatic updates are not performed if `update` is null and the
		// `editor` parameter is passed in - the reason being that Editor will
		// update the data once submitted
		var update = this.c.update !== null ?
			this.c.update :
			this.c.editor ?
				false :
				true;

		if ( update ) {
			for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
				for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
					cell = cells[i][j];

					cell.cell.data( cell.set );
				}
			}

			dt.draw(false);
		}

		this._emitEvent( 'autoFill', [ dt, cells ] );
	}
} );


/**
 * AutoFill actions. The options here determine how AutoFill will fill the data
 * in the table when the user has selected a range of cells. Please see the
 * documentation on the DataTables site for full details on how to create plug-
 * ins.
 *
 * @type {Object}
 */
AutoFill.actions = {
	increment: {
		available: function ( dt, cells ) {
			return $.isNumeric( cells[0][0].label );
		},

		option: function ( dt, cells ) {
			return dt.i18n(
				'autoFill.increment',
				'Increment / decrement each cell by: <input type="number" value="1">'
			);
		},

		execute: function ( dt, cells, node ) {
			var value = cells[0][0].data * 1;
			var increment = $('input', node).val() * 1;

			for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
				for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
					cells[i][j].set = value;

					value += increment;
				}
			}
		}
	},

	fill: {
		available: function ( dt, cells ) {
			return true;
		},

		option: function ( dt, cells ) {
			return dt.i18n('autoFill.fill', 'Fill all cells with <i>'+cells[0][0].label+'</i>' );
		},

		execute: function ( dt, cells, node ) {
			var value = cells[0][0].data;

			for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
				for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
					cells[i][j].set = value;
				}
			}
		}
	},

	fillHorizontal: {
		available: function ( dt, cells ) {
			return cells.length > 1 && cells[0].length > 1;
		},

		option: function ( dt, cells ) {
			return dt.i18n('autoFill.fillHorizontal', 'Fill cells horizontally' );
		},

		execute: function ( dt, cells, node ) {
			for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
				for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
					cells[i][j].set = cells[i][0].data;
				}
			}
		}
	},

	fillVertical: {
		available: function ( dt, cells ) {
			return cells.length > 1 && cells[0].length > 1;
		},

		option: function ( dt, cells ) {
			return dt.i18n('autoFill.fillVertical', 'Fill cells vertically' );
		},

		execute: function ( dt, cells, node ) {
			for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
				for ( var j=0, jen=cells[i].length ; j<jen ; j++ ) {
					cells[i][j].set = cells[0][j].data;
				}
			}
		}
	},

	// Special type that does not make itself available, but is added
	// automatically by AutoFill if a multi-choice list is shown. This allows
	// sensible code reuse
	cancel: {
		available: function () {
			return false;
		},

		option: function ( dt ) {
			return dt.i18n('autoFill.cancel', 'Cancel' );
		},

		execute: function () {
			return false;
		}
	}
};


/**
 * AutoFill version
 * 
 * @static
 * @type      String
 */
AutoFill.version = '2.1.2';


/**
 * AutoFill defaults
 * 
 * @namespace
 */
AutoFill.defaults = {
	/** @type {Boolean} Ask user what they want to do, even for a single option */
	alwaysAsk: false,

	/** @type {string|null} What will trigger a focus */
	focus: null, // focus, click, hover

	/** @type {column-selector} Columns to provide auto fill for */
	columns: '', // all

	/** @type {boolean|null} Update the cells after a drag */
	update: null, // false is editor given, true otherwise

	/** @type {DataTable.Editor} Editor instance for automatic submission */
	editor: null
};


/**
 * Classes used by AutoFill that are configurable
 * 
 * @namespace
 */
AutoFill.classes = {
	/** @type {String} Class used by the selection button */
	btn: 'btn'
};


// Attach a listener to the document which listens for DataTables initialisation
// events so we can automatically initialise
$(document).on( 'preInit.dt.autofill', function (e, settings, json) {
	if ( e.namespace !== 'dt' ) {
		return;
	}

	var init = settings.oInit.autoFill;
	var defaults = DataTable.defaults.autoFill;

	if ( init || defaults ) {
		var opts = $.extend( {}, init, defaults );

		if ( init !== false ) {
			new AutoFill( settings, opts  );
		}
	}
} );


// Alias for access
DataTable.AutoFill = AutoFill;
DataTable.AutoFill = AutoFill;


return AutoFill;
}));


/*! Buttons for DataTables 1.2.1
 * ©2016 SpryMedia Ltd - datatables.net/license
 */

(function( factory ){
	if ( typeof define === 'function' && define.amd ) {
		// AMD
		define( ['jquery', 'datatables.net'], function ( $ ) {
			return factory( $, window, document );
		} );
	}
	else if ( typeof exports === 'object' ) {
		// CommonJS
		module.exports = function (root, $) {
			if ( ! root ) {
				root = window;
			}

			if ( ! $ || ! $.fn.dataTable ) {
				$ = require('datatables.net')(root, $).$;
			}

			return factory( $, root, root.document );
		};
	}
	else {
		// Browser
		factory( jQuery, window, document );
	}
}(function( $, window, document, undefined ) {
'use strict';
var DataTable = $.fn.dataTable;


// Used for namespacing events added to the document by each instance, so they
// can be removed on destroy
var _instCounter = 0;

// Button namespacing counter for namespacing events on individual buttons
var _buttonCounter = 0;

var _dtButtons = DataTable.ext.buttons;

/**
 * [Buttons description]
 * @param {[type]}
 * @param {[type]}
 */
var Buttons = function( dt, config )
{
	// Allow a boolean true for defaults
	if ( config === true ) {
		config = {};
	}

	// For easy configuration of buttons an array can be given
	if ( $.isArray( config ) ) {
		config = { buttons: config };
	}

	this.c = $.extend( true, {}, Buttons.defaults, config );

	// Don't want a deep copy for the buttons
	if ( config.buttons ) {
		this.c.buttons = config.buttons;
	}

	this.s = {
		dt: new DataTable.Api( dt ),
		buttons: [],
		listenKeys: '',
		namespace: 'dtb'+(_instCounter++)
	};

	this.dom = {
		container: $('<'+this.c.dom.container.tag+'/>')
			.addClass( this.c.dom.container.className )
	};

	this._constructor();
};


$.extend( Buttons.prototype, {
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Public methods
	 */

	/**
	 * Get the action of a button
	 * @param  {int|string} Button index
	 * @return {function}
	 *//**
	 * Set the action of a button
	 * @param  {node} node Button element
	 * @param  {function} action Function to set
	 * @return {Buttons} Self for chaining
	 */
	action: function ( node, action )
	{
		var button = this._nodeToButton( node );

		if ( action === undefined ) {
			return button.conf.action;
		}

		button.conf.action = action;

		return this;
	},

	/**
	 * Add an active class to the button to make to look active or get current
	 * active state.
	 * @param  {node} node Button element
	 * @param  {boolean} [flag] Enable / disable flag
	 * @return {Buttons} Self for chaining or boolean for getter
	 */
	active: function ( node, flag ) {
		var button = this._nodeToButton( node );
		var klass = this.c.dom.button.active;
		var jqNode = $(button.node);

		if ( flag === undefined ) {
			return jqNode.hasClass( klass );
		}

		jqNode.toggleClass( klass, flag === undefined ? true : flag );

		return this;
	},

	/**
	 * Add a new button
	 * @param {object} config Button configuration object, base string name or function
	 * @param {int|string} [idx] Button index for where to insert the button
	 * @return {Buttons} Self for chaining
	 */
	add: function ( config, idx )
	{
		var buttons = this.s.buttons;

		if ( typeof idx === 'string' ) {
			var split = idx.split('-');
			var base = this.s;

			for ( var i=0, ien=split.length-1 ; i<ien ; i++ ) {
				base = base.buttons[ split[i]*1 ];
			}

			buttons = base.buttons;
			idx = split[ split.length-1 ]*1;
		}

		this._expandButton( buttons, config, false, idx );
		this._draw();

		return this;
	},

	/**
	 * Get the container node for the buttons
	 * @return {jQuery} Buttons node
	 */
	container: function ()
	{
		return this.dom.container;
	},

	/**
	 * Disable a button
	 * @param  {node} node Button node
	 * @return {Buttons} Self for chaining
	 */
	disable: function ( node ) {
		var button = this._nodeToButton( node );

		$(button.node).addClass( this.c.dom.button.disabled );

		return this;
	},

	/**
	 * Destroy the instance, cleaning up event handlers and removing DOM
	 * elements
	 * @return {Buttons} Self for chaining
	 */
	destroy: function ()
	{
		// Key event listener
		$('body').off( 'keyup.'+this.s.namespace );

		// Individual button destroy (so they can remove their own events if
		// needed). Take a copy as the array is modified by `remove`
		var buttons = this.s.buttons.slice();
		var i, ien;
		
		for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
			this.remove( buttons[i].node );
		}

		// Container
		this.dom.container.remove();

		// Remove from the settings object collection
		var buttonInsts = this.s.dt.settings()[0];

		for ( i=0, ien=buttonInsts.length ; i<ien ; i++ ) {
			if ( buttonInsts.inst === this ) {
				buttonInsts.splice( i, 1 );
				break;
			}
		}

		return this;
	},

	/**
	 * Enable / disable a button
	 * @param  {node} node Button node
	 * @param  {boolean} [flag=true] Enable / disable flag
	 * @return {Buttons} Self for chaining
	 */
	enable: function ( node, flag )
	{
		if ( flag === false ) {
			return this.disable( node );
		}

		var button = this._nodeToButton( node );
		$(button.node).removeClass( this.c.dom.button.disabled );

		return this;
	},

	/**
	 * Get the instance name for the button set selector
	 * @return {string} Instance name
	 */
	name: function ()
	{
		return this.c.name;
	},

	/**
	 * Get a button's node
	 * @param  {node} node Button node
	 * @return {jQuery} Button element
	 */
	node: function ( node )
	{
		var button = this._nodeToButton( node );
		return $(button.node);
	},

	/**
	 * Remove a button.
	 * @param  {node} node Button node
	 * @return {Buttons} Self for chaining
	 */
	remove: function ( node )
	{
		var button = this._nodeToButton( node );
		var host = this._nodeToHost( node );
		var dt = this.s.dt;

		// Remove any child buttons first
		if ( button.buttons.length ) {
			for ( var i=button.buttons.length-1 ; i>=0 ; i-- ) {
				this.remove( button.buttons[i].node );
			}
		}

		// Allow the button to remove event handlers, etc
		if ( button.conf.destroy ) {
			button.conf.destroy.call( dt.button(node), dt, $(node), button.conf );
		}

		this._removeKey( button.conf );

		$(button.node).remove();

		var idx = $.inArray( button, host );
		host.splice( idx, 1 );

		return this;
	},

	/**
	 * Get the text for a button
	 * @param  {int|string} node Button index
	 * @return {string} Button text
	 *//**
	 * Set the text for a button
	 * @param  {int|string|function} node Button index
	 * @param  {string} label Text
	 * @return {Buttons} Self for chaining
	 */
	text: function ( node, label )
	{
		var button = this._nodeToButton( node );
		var buttonLiner = this.c.dom.collection.buttonLiner;
		var linerTag = button.inCollection && buttonLiner && buttonLiner.tag ?
			buttonLiner.tag :
			this.c.dom.buttonLiner.tag;
		var dt = this.s.dt;
		var jqNode = $(button.node);
		var text = function ( opt ) {
			return typeof opt === 'function' ?
				opt( dt, jqNode, button.conf ) :
				opt;
		};

		if ( label === undefined ) {
			return text( button.conf.text );
		}

		button.conf.text = label;

		if ( linerTag ) {
			jqNode.children( linerTag ).html( text(label) );
		}
		else {
			jqNode.html( text(label) );
		}

		return this;
	},


	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Constructor
	 */

	/**
	 * Buttons constructor
	 * @private
	 */
	_constructor: function ()
	{
		var that = this;
		var dt = this.s.dt;
		var dtSettings = dt.settings()[0];
		var buttons =  this.c.buttons;

		if ( ! dtSettings._buttons ) {
			dtSettings._buttons = [];
		}

		dtSettings._buttons.push( {
			inst: this,
			name: this.c.name
		} );

		for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
			this.add( buttons[i] );
		}

		dt.on( 'destroy', function () {
			that.destroy();
		} );

		// Global key event binding to listen for button keys
		$('body').on( 'keyup.'+this.s.namespace, function ( e ) {
			if ( ! document.activeElement || document.activeElement === document.body ) {
				// SUse a string of characters for fast lookup of if we need to
				// handle this
				var character = String.fromCharCode(e.keyCode).toLowerCase();

				if ( that.s.listenKeys.toLowerCase().indexOf( character ) !== -1 ) {
					that._keypress( character, e );
				}
			}
		} );
	},


	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Private methods
	 */

	/**
	 * Add a new button to the key press listener
	 * @param {object} conf Resolved button configuration object
	 * @private
	 */
	_addKey: function ( conf )
	{
		if ( conf.key ) {
			this.s.listenKeys += $.isPlainObject( conf.key ) ?
				conf.key.key :
				conf.key;
		}
	},

	/**
	 * Insert the buttons into the container. Call without parameters!
	 * @param  {node} [container] Recursive only - Insert point
	 * @param  {array} [buttons] Recursive only - Buttons array
	 * @private
	 */
	_draw: function ( container, buttons )
	{
		if ( ! container ) {
			container = this.dom.container;
			buttons = this.s.buttons;
		}

		container.children().detach();

		for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
			container.append( buttons[i].inserter );

			if ( buttons[i].buttons && buttons[i].buttons.length ) {
				this._draw( buttons[i].collection, buttons[i].buttons );
			}
		}
	},

	/**
	 * Create buttons from an array of buttons
	 * @param  {array} attachTo Buttons array to attach to
	 * @param  {object} button Button definition
	 * @param  {boolean} inCollection true if the button is in a collection
	 * @private
	 */
	_expandButton: function ( attachTo, button, inCollection, attachPoint )
	{
		var dt = this.s.dt;
		var buttonCounter = 0;
		var buttons = ! $.isArray( button ) ?
			[ button ] :
			button;

		for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
			var conf = this._resolveExtends( buttons[i] );

			if ( ! conf ) {
				continue;
			}

			// If the configuration is an array, then expand the buttons at this
			// point
			if ( $.isArray( conf ) ) {
				this._expandButton( attachTo, conf, inCollection, attachPoint );
				continue;
			}

			var built = this._buildButton( conf, inCollection );
			if ( ! built ) {
				continue;
			}

			if ( attachPoint !== undefined ) {
				attachTo.splice( attachPoint, 0, built );
				attachPoint++;
			}
			else {
				attachTo.push( built );
			}

			if ( built.conf.buttons ) {
				var collectionDom = this.c.dom.collection;
				built.collection = $('<'+collectionDom.tag+'/>')
					.addClass( collectionDom.className );
				built.conf._collection = built.collection;

				this._expandButton( built.buttons, built.conf.buttons, true, attachPoint );
			}

			// init call is made here, rather than buildButton as it needs to
			// be selectable, and for that it needs to be in the buttons array
			if ( conf.init ) {
				conf.init.call( dt.button( built.node ), dt, $(built.node), conf );
			}

			buttonCounter++;
		}
	},

	/**
	 * Create an individual button
	 * @param  {object} config            Resolved button configuration
	 * @param  {boolean} inCollection `true` if a collection button
	 * @return {jQuery} Created button node (jQuery)
	 * @private
	 */
	_buildButton: function ( config, inCollection )
	{
		var buttonDom = this.c.dom.button;
		var linerDom = this.c.dom.buttonLiner;
		var collectionDom = this.c.dom.collection;
		var dt = this.s.dt;
		var text = function ( opt ) {
			return typeof opt === 'function' ?
				opt( dt, button, config ) :
				opt;
		};

		if ( inCollection && collectionDom.button ) {
			buttonDom = collectionDom.button;
		}

		if ( inCollection && collectionDom.buttonLiner ) {
			linerDom = collectionDom.buttonLiner;
		}

		// Make sure that the button is available based on whatever requirements
		// it has. For example, Flash buttons require Flash
		if ( config.available && ! config.available( dt, config ) ) {
			return false;
		}

		var action = function ( e, dt, button, config ) {
			config.action.call( dt.button( button ), e, dt, button, config );

			$(dt.table().node()).triggerHandler( 'buttons-action.dt', [
				dt.button( button ), dt, button, config 
			] );
		};

		var button = $('<'+buttonDom.tag+'/>')
			.addClass( buttonDom.className )
			.attr( 'tabindex', this.s.dt.settings()[0].iTabIndex )
			.attr( 'aria-controls', this.s.dt.table().node().id )
			.on( 'click.dtb', function (e) {
				e.preventDefault();

				if ( ! button.hasClass( buttonDom.disabled ) && config.action ) {
					action( e, dt, button, config );
				}

				button.blur();
			} )
			.on( 'keyup.dtb', function (e) {
				if ( e.keyCode === 13 ) {
					if ( ! button.hasClass( buttonDom.disabled ) && config.action ) {
						action( e, dt, button, config );
					}
				}
			} );

		// Make `a` tags act like a link
		if ( buttonDom.tag.toLowerCase() === 'a' ) {
			button.attr( 'href', '#' );
		}

		if ( linerDom.tag ) {
			var liner = $('<'+linerDom.tag+'/>')
				.html( text( config.text ) )
				.addClass( linerDom.className );

			if ( linerDom.tag.toLowerCase() === 'a' ) {
				liner.attr( 'href', '#' );
			}

			button.append( liner );
		}
		else {
			button.html( text( config.text ) );
		}

		if ( config.enabled === false ) {
			button.addClass( buttonDom.disabled );
		}

		if ( config.className ) {
			button.addClass( config.className );
		}

		if ( config.titleAttr ) {
			button.attr( 'title', config.titleAttr );
		}

		if ( ! config.namespace ) {
			config.namespace = '.dt-button-'+(_buttonCounter++);
		}

		var buttonContainer = this.c.dom.buttonContainer;
		var inserter;
		if ( buttonContainer && buttonContainer.tag ) {
			inserter = $('<'+buttonContainer.tag+'/>')
				.addClass( buttonContainer.className )
				.append( button );
		}
		else {
			inserter = button;
		}

		this._addKey( config );

		return {
			conf:         config,
			node:         button.get(0),
			inserter:     inserter,
			buttons:      [],
			inCollection: inCollection,
			collection:   null
		};
	},

	/**
	 * Get the button object from a node (recursive)
	 * @param  {node} node Button node
	 * @param  {array} [buttons] Button array, uses base if not defined
	 * @return {object} Button object
	 * @private
	 */
	_nodeToButton: function ( node, buttons )
	{
		if ( ! buttons ) {
			buttons = this.s.buttons;
		}

		for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
			if ( buttons[i].node === node ) {
				return buttons[i];
			}

			if ( buttons[i].buttons.length ) {
				var ret = this._nodeToButton( node, buttons[i].buttons );

				if ( ret ) {
					return ret;
				}
			}
		}
	},

	/**
	 * Get container array for a button from a button node (recursive)
	 * @param  {node} node Button node
	 * @param  {array} [buttons] Button array, uses base if not defined
	 * @return {array} Button's host array
	 * @private
	 */
	_nodeToHost: function ( node, buttons )
	{
		if ( ! buttons ) {
			buttons = this.s.buttons;
		}

		for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
			if ( buttons[i].node === node ) {
				return buttons;
			}

			if ( buttons[i].buttons.length ) {
				var ret = this._nodeToHost( node, buttons[i].buttons );

				if ( ret ) {
					return ret;
				}
			}
		}
	},

	/**
	 * Handle a key press - determine if any button's key configured matches
	 * what was typed and trigger the action if so.
	 * @param  {string} character The character pressed
	 * @param  {object} e Key event that triggered this call
	 * @private
	 */
	_keypress: function ( character, e )
	{
		var run = function ( conf, node ) {
			if ( ! conf.key ) {
				return;
			}

			if ( conf.key === character ) {
				$(node).click();
			}
			else if ( $.isPlainObject( conf.key ) ) {
				if ( conf.key.key !== character ) {
					return;
				}

				if ( conf.key.shiftKey && ! e.shiftKey ) {
					return;
				}

				if ( conf.key.altKey && ! e.altKey ) {
					return;
				}

				if ( conf.key.ctrlKey && ! e.ctrlKey ) {
					return;
				}

				if ( conf.key.metaKey && ! e.metaKey ) {
					return;
				}

				// Made it this far - it is good
				$(node).click();
			}
		};

		var recurse = function ( a ) {
			for ( var i=0, ien=a.length ; i<ien ; i++ ) {
				run( a[i].conf, a[i].node );

				if ( a[i].buttons.length ) {
					recurse( a[i].buttons );
				}
			}
		};

		recurse( this.s.buttons );
	},

	/**
	 * Remove a key from the key listener for this instance (to be used when a
	 * button is removed)
	 * @param  {object} conf Button configuration
	 * @private
	 */
	_removeKey: function ( conf )
	{
		if ( conf.key ) {
			var character = $.isPlainObject( conf.key ) ?
				conf.key.key :
				conf.key;

			// Remove only one character, as multiple buttons could have the
			// same listening key
			var a = this.s.listenKeys.split('');
			var idx = $.inArray( character, a );
			a.splice( idx, 1 );
			this.s.listenKeys = a.join('');
		}
	},

	/**
	 * Resolve a button configuration
	 * @param  {string|function|object} conf Button config to resolve
	 * @return {object} Button configuration
	 * @private
	 */
	_resolveExtends: function ( conf )
	{
		var dt = this.s.dt;
		var i, ien;
		var toConfObject = function ( base ) {
			var loop = 0;

			// Loop until we have resolved to a button configuration, or an
			// array of button configurations (which will be iterated
			// separately)
			while ( ! $.isPlainObject(base) && ! $.isArray(base) ) {
				if ( base === undefined ) {
					return;
				}

				if ( typeof base === 'function' ) {
					base = base( dt, conf );

					if ( ! base ) {
						return false;
					}
				}
				else if ( typeof base === 'string' ) {
					if ( ! _dtButtons[ base ] ) {
						throw 'Unknown button type: '+base;
					}

					base = _dtButtons[ base ];
				}

				loop++;
				if ( loop > 30 ) {
					// Protect against misconfiguration killing the browser
					throw 'Buttons: Too many iterations';
				}
			}

			return $.isArray( base ) ?
				base :
				$.extend( {}, base );
		};

		conf = toConfObject( conf );

		while ( conf && conf.extend ) {
			// Use `toConfObject` in case the button definition being extended
			// is itself a string or a function
			if ( ! _dtButtons[ conf.extend ] ) {
				throw 'Cannot extend unknown button type: '+conf.extend;
			}

			var objArray = toConfObject( _dtButtons[ conf.extend ] );
			if ( $.isArray( objArray ) ) {
				return objArray;
			}
			else if ( ! objArray ) {
				// This is a little brutal as it might be possible to have a
				// valid button without the extend, but if there is no extend
				// then the host button would be acting in an undefined state
				return false;
			}

			// Stash the current class name
			var originalClassName = objArray.className;

			conf = $.extend( {}, objArray, conf );

			// The extend will have overwritten the original class name if the
			// `conf` object also assigned a class, but we want to concatenate
			// them so they are list that is combined from all extended buttons
			if ( originalClassName && conf.className !== originalClassName ) {
				conf.className = originalClassName+' '+conf.className;
			}

			// Buttons to be added to a collection  -gives the ability to define
			// if buttons should be added to the start or end of a collection
			var postfixButtons = conf.postfixButtons;
			if ( postfixButtons ) {
				if ( ! conf.buttons ) {
					conf.buttons = [];
				}

				for ( i=0, ien=postfixButtons.length ; i<ien ; i++ ) {
					conf.buttons.push( postfixButtons[i] );
				}

				conf.postfixButtons = null;
			}

			var prefixButtons = conf.prefixButtons;
			if ( prefixButtons ) {
				if ( ! conf.buttons ) {
					conf.buttons = [];
				}

				for ( i=0, ien=prefixButtons.length ; i<ien ; i++ ) {
					conf.buttons.splice( i, 0, prefixButtons[i] );
				}

				conf.prefixButtons = null;
			}

			// Although we want the `conf` object to overwrite almost all of
			// the properties of the object being extended, the `extend`
			// property should come from the object being extended
			conf.extend = objArray.extend;
		}

		return conf;
	}
} );



/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Statics
 */

/**
 * Show / hide a background layer behind a collection
 * @param  {boolean} Flag to indicate if the background should be shown or
 *   hidden 
 * @param  {string} Class to assign to the background
 * @static
 */
Buttons.background = function ( show, className, fade ) {
	if ( fade === undefined ) {
		fade = 400;
	}

	if ( show ) {
		$('<div/>')
			.addClass( className )
			.css( 'display', 'none' )
			.appendTo( 'body' )
			.fadeIn( fade );
	}
	else {
		$('body > div.'+className)
			.fadeOut( fade, function () {
				$(this).remove();
			} );
	}
};

/**
 * Instance selector - select Buttons instances based on an instance selector
 * value from the buttons assigned to a DataTable. This is only useful if
 * multiple instances are attached to a DataTable.
 * @param  {string|int|array} Instance selector - see `instance-selector`
 *   documentation on the DataTables site
 * @param  {array} Button instance array that was attached to the DataTables
 *   settings object
 * @return {array} Buttons instances
 * @static
 */
Buttons.instanceSelector = function ( group, buttons )
{
	if ( ! group ) {
		return $.map( buttons, function ( v ) {
			return v.inst;
		} );
	}

	var ret = [];
	var names = $.map( buttons, function ( v ) {
		return v.name;
	} );

	// Flatten the group selector into an array of single options
	var process = function ( input ) {
		if ( $.isArray( input ) ) {
			for ( var i=0, ien=input.length ; i<ien ; i++ ) {
				process( input[i] );
			}
			return;
		}

		if ( typeof input === 'string' ) {
			if ( input.indexOf( ',' ) !== -1 ) {
				// String selector, list of names
				process( input.split(',') );
			}
			else {
				// String selector individual name
				var idx = $.inArray( $.trim(input), names );

				if ( idx !== -1 ) {
					ret.push( buttons[ idx ].inst );
				}
			}
		}
		else if ( typeof input === 'number' ) {
			// Index selector
			ret.push( buttons[ input ].inst );
		}
	};
	
	process( group );

	return ret;
};

/**
 * Button selector - select one or more buttons from a selector input so some
 * operation can be performed on them.
 * @param  {array} Button instances array that the selector should operate on
 * @param  {string|int|node|jQuery|array} Button selector - see
 *   `button-selector` documentation on the DataTables site
 * @return {array} Array of objects containing `inst` and `idx` properties of
 *   the selected buttons so you know which instance each button belongs to.
 * @static
 */
Buttons.buttonSelector = function ( insts, selector )
{
	var ret = [];
	var nodeBuilder = function ( a, buttons, baseIdx ) {
		var button;
		var idx;

		for ( var i=0, ien=buttons.length ; i<ien ; i++ ) {
			button = buttons[i];

			if ( button ) {
				idx = baseIdx !== undefined ?
					baseIdx+i :
					i+'';

				a.push( {
					node: button.node,
					name: button.conf.name,
					idx:  idx
				} );

				if ( button.buttons ) {
					nodeBuilder( a, button.buttons, idx+'-' );
				}
			}
		}
	};

	var run = function ( selector, inst ) {
		var i, ien;
		var buttons = [];
		nodeBuilder( buttons, inst.s.buttons );

		var nodes = $.map( buttons, function (v) {
			return v.node;
		} );

		if ( $.isArray( selector ) || selector instanceof $ ) {
			for ( i=0, ien=selector.length ; i<ien ; i++ ) {
				run( selector[i], inst );
			}
			return;
		}

		if ( selector === null || selector === undefined || selector === '*' ) {
			// Select all
			for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
				ret.push( {
					inst: inst,
					node: buttons[i].node
				} );
			}
		}
		else if ( typeof selector === 'number' ) {
			// Main button index selector
			ret.push( {
				inst: inst,
				node: inst.s.buttons[ selector ].node
			} );
		}
		else if ( typeof selector === 'string' ) {
			if ( selector.indexOf( ',' ) !== -1 ) {
				// Split
				var a = selector.split(',');

				for ( i=0, ien=a.length ; i<ien ; i++ ) {
					run( $.trim(a[i]), inst );
				}
			}
			else if ( selector.match( /^\d+(\-\d+)*$/ ) ) {
				// Sub-button index selector
				var indexes = $.map( buttons, function (v) {
					return v.idx;
				} );

				ret.push( {
					inst: inst,
					node: buttons[ $.inArray( selector, indexes ) ].node
				} );
			}
			else if ( selector.indexOf( ':name' ) !== -1 ) {
				// Button name selector
				var name = selector.replace( ':name', '' );

				for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
					if ( buttons[i].name === name ) {
						ret.push( {
							inst: inst,
							node: buttons[i].node
						} );
					}
				}
			}
			else {
				// jQuery selector on the nodes
				$( nodes ).filter( selector ).each( function () {
					ret.push( {
						inst: inst,
						node: this
					} );
				} );
			}
		}
		else if ( typeof selector === 'object' && selector.nodeName ) {
			// Node selector
			var idx = $.inArray( selector, nodes );

			if ( idx !== -1 ) {
				ret.push( {
					inst: inst,
					node: nodes[ idx ]
				} );
			}
		}
	};


	for ( var i=0, ien=insts.length ; i<ien ; i++ ) {
		var inst = insts[i];

		run( selector, inst );
	}

	return ret;
};


/**
 * Buttons defaults. For full documentation, please refer to the docs/option
 * directory or the DataTables site.
 * @type {Object}
 * @static
 */
Buttons.defaults = {
	buttons: [ 'copy', 'excel', 'csv', 'pdf', 'print' ],
	name: 'main',
	tabIndex: 0,
	dom: {
		container: {
			tag: 'div',
			className: 'dt-buttons'
		},
		collection: {
			tag: 'div',
			className: 'dt-button-collection'
		},
		button: {
			tag: 'a',
			className: 'dt-button',
			active: 'active',
			disabled: 'disabled'
		},
		buttonLiner: {
			tag: 'span',
			className: ''
		}
	}
};

/**
 * Version information
 * @type {string}
 * @static
 */
Buttons.version = '1.2.1';


$.extend( _dtButtons, {
	collection: {
		text: function ( dt ) {
			return dt.i18n( 'buttons.collection', 'Collection' );
		},
		className: 'buttons-collection',
		action: function ( e, dt, button, config ) {
			var host = button;
			var hostOffset = host.offset();
			var tableContainer = $( dt.table().container() );
			var multiLevel = false;

			// Remove any old collection
			if ( $('div.dt-button-background').length ) {
				multiLevel = $('div.dt-button-collection').offset();
				$('body').trigger( 'click.dtb-collection' );
			}

			config._collection
				.addClass( config.collectionLayout )
				.css( 'display', 'none' )
				.appendTo( 'body' )
				.fadeIn( config.fade );

			var position = config._collection.css( 'position' );

			if ( multiLevel && position === 'absolute' ) {
				config._collection.css( {
					top: multiLevel.top + 5, // magic numbers for a little offset
					left: multiLevel.left + 5
				} );
			}
			else if ( position === 'absolute' ) {
				config._collection.css( {
					top: hostOffset.top + host.outerHeight(),
					left: hostOffset.left
				} );

				var listRight = hostOffset.left + config._collection.outerWidth();
				var tableRight = tableContainer.offset().left + tableContainer.width();
				if ( listRight > tableRight ) {
					config._collection.css( 'left', hostOffset.left - ( listRight - tableRight ) );
				}
			}
			else {
				// Fix position - centre on screen
				var top = config._collection.height() / 2;
				if ( top > $(window).height() / 2 ) {
					top = $(window).height() / 2;
				}

				config._collection.css( 'marginTop', top*-1 );
			}

			if ( config.background ) {
				Buttons.background( true, config.backgroundClassName, config.fade );
			}

			// Need to break the 'thread' for the collection button being
			// activated by a click - it would also trigger this event
			setTimeout( function () {
				// This is bonkers, but if we don't have a click listener on the
				// background element, iOS Safari will ignore the body click
				// listener below. An empty function here is all that is
				// required to make it work...
				$('div.dt-button-background').on( 'click.dtb-collection', function () {} );

				$('body').on( 'click.dtb-collection', function (e) {
					if ( ! $(e.target).parents().andSelf().filter( config._collection ).length ) {
						config._collection
							.fadeOut( config.fade, function () {
								config._collection.detach();
							} );

						$('div.dt-button-background').off( 'click.dtb-collection' );
						Buttons.background( false, config.backgroundClassName, config.fade );

						$('body').off( 'click.dtb-collection' );
						dt.off( 'buttons-action.b-internal' );
					}
				} );
			}, 10 );

			if ( config.autoClose ) {
				dt.on( 'buttons-action.b-internal', function () {
					$('div.dt-button-background').click();
				} );
			}
		},
		background: true,
		collectionLayout: '',
		backgroundClassName: 'dt-button-background',
		autoClose: false,
		fade: 400
	},
	copy: function ( dt, conf ) {
		if ( _dtButtons.copyHtml5 ) {
			return 'copyHtml5';
		}
		if ( _dtButtons.copyFlash && _dtButtons.copyFlash.available( dt, conf ) ) {
			return 'copyFlash';
		}
	},
	csv: function ( dt, conf ) {
		// Common option that will use the HTML5 or Flash export buttons
		if ( _dtButtons.csvHtml5 && _dtButtons.csvHtml5.available( dt, conf ) ) {
			return 'csvHtml5';
		}
		if ( _dtButtons.csvFlash && _dtButtons.csvFlash.available( dt, conf ) ) {
			return 'csvFlash';
		}
	},
	excel: function ( dt, conf ) {
		// Common option that will use the HTML5 or Flash export buttons
		if ( _dtButtons.excelHtml5 && _dtButtons.excelHtml5.available( dt, conf ) ) {
			return 'excelHtml5';
		}
		if ( _dtButtons.excelFlash && _dtButtons.excelFlash.available( dt, conf ) ) {
			return 'excelFlash';
		}
	},
	pdf: function ( dt, conf ) {
		// Common option that will use the HTML5 or Flash export buttons
		if ( _dtButtons.pdfHtml5 && _dtButtons.pdfHtml5.available( dt, conf ) ) {
			return 'pdfHtml5';
		}
		if ( _dtButtons.pdfFlash && _dtButtons.pdfFlash.available( dt, conf ) ) {
			return 'pdfFlash';
		}
	},
	pageLength: function ( dt ) {
		var lengthMenu = dt.settings()[0].aLengthMenu;
		var vals = $.isArray( lengthMenu[0] ) ? lengthMenu[0] : lengthMenu;
		var lang = $.isArray( lengthMenu[0] ) ? lengthMenu[1] : lengthMenu;
		var text = function ( dt ) {
			return dt.i18n( 'buttons.pageLength', {
				"-1": 'Show all rows',
				_:    'Show %d rows'
			}, dt.page.len() );
		};

		return {
			extend: 'collection',
			text: text,
			className: 'buttons-page-length',
			autoClose: true,
			buttons: $.map( vals, function ( val, i ) {
				return {
					text: lang[i],
					action: function ( e, dt ) {
						dt.page.len( val ).draw();
					},
					init: function ( dt, node, conf ) {
						var that = this;
						var fn = function () {
							that.active( dt.page.len() === val );
						};

						dt.on( 'length.dt'+conf.namespace, fn );
						fn();
					},
					destroy: function ( dt, node, conf ) {
						dt.off( 'length.dt'+conf.namespace );
					}
				};
			} ),
			init: function ( dt, node, conf ) {
				var that = this;
				dt.on( 'length.dt'+conf.namespace, function () {
					that.text( text( dt ) );
				} );
			},
			destroy: function ( dt, node, conf ) {
				dt.off( 'length.dt'+conf.namespace );
			}
		};
	}
} );


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * DataTables API
 *
 * For complete documentation, please refer to the docs/api directory or the
 * DataTables site
 */

// Buttons group and individual button selector
DataTable.Api.register( 'buttons()', function ( group, selector ) {
	// Argument shifting
	if ( selector === undefined ) {
		selector = group;
		group = undefined;
	}

	return this.iterator( true, 'table', function ( ctx ) {
		if ( ctx._buttons ) {
			return Buttons.buttonSelector(
				Buttons.instanceSelector( group, ctx._buttons ),
				selector
			);
		}
	}, true );
} );

// Individual button selector
DataTable.Api.register( 'button()', function ( group, selector ) {
	// just run buttons() and truncate
	var buttons = this.buttons( group, selector );

	if ( buttons.length > 1 ) {
		buttons.splice( 1, buttons.length );
	}

	return buttons;
} );

// Active buttons
DataTable.Api.registerPlural( 'buttons().active()', 'button().active()', function ( flag ) {
	if ( flag === undefined ) {
		return this.map( function ( set ) {
			return set.inst.active( set.node );
		} );
	}

	return this.each( function ( set ) {
		set.inst.active( set.node, flag );
	} );
} );

// Get / set button action
DataTable.Api.registerPlural( 'buttons().action()', 'button().action()', function ( action ) {
	if ( action === undefined ) {
		return this.map( function ( set ) {
			return set.inst.action( set.node );
		} );
	}

	return this.each( function ( set ) {
		set.inst.action( set.node, action );
	} );
} );

// Enable / disable buttons
DataTable.Api.register( ['buttons().enable()', 'button().enable()'], function ( flag ) {
	return this.each( function ( set ) {
		set.inst.enable( set.node, flag );
	} );
} );

// Disable buttons
DataTable.Api.register( ['buttons().disable()', 'button().disable()'], function () {
	return this.each( function ( set ) {
		set.inst.disable( set.node );
	} );
} );

// Get button nodes
DataTable.Api.registerPlural( 'buttons().nodes()', 'button().node()', function () {
	var jq = $();

	// jQuery will automatically reduce duplicates to a single entry
	$( this.each( function ( set ) {
		jq = jq.add( set.inst.node( set.node ) );
	} ) );

	return jq;
} );

// Get / set button text (i.e. the button labels)
DataTable.Api.registerPlural( 'buttons().text()', 'button().text()', function ( label ) {
	if ( label === undefined ) {
		return this.map( function ( set ) {
			return set.inst.text( set.node );
		} );
	}

	return this.each( function ( set ) {
		set.inst.text( set.node, label );
	} );
} );

// Trigger a button's action
DataTable.Api.registerPlural( 'buttons().trigger()', 'button().trigger()', function () {
	return this.each( function ( set ) {
		set.inst.node( set.node ).trigger( 'click' );
	} );
} );

// Get the container elements for the button sets selected
DataTable.Api.registerPlural( 'buttons().containers()', 'buttons().container()', function () {
	var jq = $();

	// jQuery will automatically reduce duplicates to a single entry
	$( this.each( function ( set ) {
		jq = jq.add( set.inst.container() );
	} ) );

	return jq;
} );

// Add a new button
DataTable.Api.register( 'button().add()', function ( idx, conf ) {
	if ( this.length === 1 ) {
		this[0].inst.add( conf, idx );
	}

	return this.button( idx );
} );

// Destroy the button sets selected
DataTable.Api.register( 'buttons().destroy()', function () {
	this.pluck( 'inst' ).unique().each( function ( inst ) {
		inst.destroy();
	} );

	return this;
} );

// Remove a button
DataTable.Api.registerPlural( 'buttons().remove()', 'buttons().remove()', function () {
	this.each( function ( set ) {
		set.inst.remove( set.node );
	} );

	return this;
} );

// Information box that can be used by buttons
var _infoTimer;
DataTable.Api.register( 'buttons.info()', function ( title, message, time ) {
	var that = this;

	if ( title === false ) {
		$('#datatables_buttons_info').fadeOut( function () {
			$(this).remove();
		} );
		clearTimeout( _infoTimer );
		_infoTimer = null;

		return this;
	}

	if ( _infoTimer ) {
		clearTimeout( _infoTimer );
	}

	if ( $('#datatables_buttons_info').length ) {
		$('#datatables_buttons_info').remove();
	}

	title = title ? '<h2>'+title+'</h2>' : '';

	$('<div id="datatables_buttons_info" class="dt-button-info"/>')
		.html( title )
		.append( $('<div/>')[ typeof message === 'string' ? 'html' : 'append' ]( message ) )
		.css( 'display', 'none' )
		.appendTo( 'body' )
		.fadeIn();

	if ( time !== undefined && time !== 0 ) {
		_infoTimer = setTimeout( function () {
			that.buttons.info( false );
		}, time );
	}

	return this;
} );

// Get data from the table for export - this is common to a number of plug-in
// buttons so it is included in the Buttons core library
DataTable.Api.register( 'buttons.exportData()', function ( options ) {
	if ( this.context.length ) {
		return _exportData( new DataTable.Api( this.context[0] ), options );
	}
} );


var _exportTextarea = $('<textarea/>')[0];
var _exportData = function ( dt, inOpts )
{
	var config = $.extend( true, {}, {
		rows:           null,
		columns:        '',
		modifier:       {
			search: 'applied',
			order:  'applied'
		},
		orthogonal:     'display',
		stripHtml:      true,
		stripNewlines:  true,
		decodeEntities: true,
		trim:           true,
		format:         {
			header: function ( d ) {
				return strip( d );
			},
			footer: function ( d ) {
				return strip( d );
			},
			body: function ( d ) {
				return strip( d );
			}
		}
	}, inOpts );

	var strip = function ( str ) {
		if ( typeof str !== 'string' ) {
			return str;
		}

		if ( config.stripHtml ) {
			str = str.replace( /<[^>]*>/g, '' );
		}

		if ( config.trim ) {
			str = str.replace( /^\s+|\s+$/g, '' );
		}

		if ( config.stripNewlines ) {
			str = str.replace( /\n/g, ' ' );
		}

		if ( config.decodeEntities ) {
			_exportTextarea.innerHTML = str;
			str = _exportTextarea.value;
		}

		return str;
	};


	var header = dt.columns( config.columns ).indexes().map( function (idx) {
		return config.format.header( dt.column( idx ).header().innerHTML, idx );
	} ).toArray();

	var footer = dt.table().footer() ?
		dt.columns( config.columns ).indexes().map( function (idx) {
			var el = dt.column( idx ).footer();
			return config.format.footer( el ? el.innerHTML : '', idx );
		} ).toArray() :
		null;

	var rowIndexes = dt.rows( config.rows, config.modifier ).indexes().toArray();
	var cells = dt
		.cells( rowIndexes, config.columns )
		.render( config.orthogonal )
		.toArray();
	var columns = header.length;
	var rows = columns > 0 ? cells.length / columns : 0;
	var body = new Array( rows );
	var cellCounter = 0;

	for ( var i=0, ien=rows ; i<ien ; i++ ) {
		var row = new Array( columns );

		for ( var j=0 ; j<columns ; j++ ) {
			row[j] = config.format.body( cells[ cellCounter ], j, i );
			cellCounter++;
		}

		body[i] = row;
	}

	return {
		header: header,
		footer: footer,
		body:   body
	};
};


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * DataTables interface
 */

// Attach to DataTables objects for global access
$.fn.dataTable.Buttons = Buttons;
$.fn.DataTable.Buttons = Buttons;



// DataTables creation - check if the buttons have been defined for this table,
// they will have been if the `B` option was used in `dom`, otherwise we should
// create the buttons instance here so they can be inserted into the document
// using the API. Listen for `init` for compatibility with pre 1.10.10, but to
// be removed in future.
$(document).on( 'init.dt plugin-init.dt', function (e, settings) {
	if ( e.namespace !== 'dt' ) {
		return;
	}

	var opts = settings.oInit.buttons || DataTable.defaults.buttons;

	if ( opts && ! settings._buttons ) {
		new Buttons( settings, opts ).container();
	}
} );

// DataTables `dom` feature option
DataTable.ext.feature.push( {
	fnInit: function( settings ) {
		var api = new DataTable.Api( settings );
		var opts = api.init().buttons || DataTable.defaults.buttons;

		return new Buttons( api, opts ).container();
	},
	cFeature: "B"
} );


return Buttons;
}));


/*!
 * Column visibility buttons for Buttons and DataTables.
 * 2016 SpryMedia Ltd - datatables.net/license
 */

(function( factory ){
	if ( typeof define === 'function' && define.amd ) {
		// AMD
		define( ['jquery', 'datatables.net', 'datatables.net-buttons'], function ( $ ) {
			return factory( $, window, document );
		} );
	}
	else if ( typeof exports === 'object' ) {
		// CommonJS
		module.exports = function (root, $) {
			if ( ! root ) {
				root = window;
			}

			if ( ! $ || ! $.fn.dataTable ) {
				$ = require('datatables.net')(root, $).$;
			}

			if ( ! $.fn.dataTable.Buttons ) {
				require('datatables.net-buttons')(root, $);
			}

			return factory( $, root, root.document );
		};
	}
	else {
		// Browser
		factory( jQuery, window, document );
	}
}(function( $, window, document, undefined ) {
'use strict';
var DataTable = $.fn.dataTable;


$.extend( DataTable.ext.buttons, {
	// A collection of column visibility buttons
	colvis: function ( dt, conf ) {
		return {
			extend: 'collection',
			text: function ( dt ) {
				return dt.i18n( 'buttons.colvis', 'Column visibility' );
			},
			className: 'buttons-colvis',
			buttons: [ {
				extend: 'columnsToggle',
				columns: conf.columns
			} ]
		};
	},

	// Selected columns with individual buttons - toggle column visibility
	columnsToggle: function ( dt, conf ) {
		var columns = dt.columns( conf.columns ).indexes().map( function ( idx ) {
			return {
				extend: 'columnToggle',
				columns: idx
			};
		} ).toArray();

		return columns;
	},

	// Single button to toggle column visibility
	columnToggle: function ( dt, conf ) {
		return {
			extend: 'columnVisibility',
			columns: conf.columns
		};
	},

	// Selected columns with individual buttons - set column visibility
	columnsVisibility: function ( dt, conf ) {
		var columns = dt.columns( conf.columns ).indexes().map( function ( idx ) {
			return {
				extend: 'columnVisibility',
				columns: idx,
				visibility: conf.visibility
			};
		} ).toArray();

		return columns;
	},

	// Single button to set column visibility
	columnVisibility: {
		columns: undefined, // column selector
		text: function ( dt, button, conf ) {
			return conf._columnText( dt, conf.columns );
		},
		className: 'buttons-columnVisibility',
		action: function ( e, dt, button, conf ) {
			var col = dt.columns( conf.columns );
			var curr = col.visible();

			col.visible( conf.visibility !== undefined ?
				conf.visibility :
				! (curr.length ? curr[0] : false )
			);
		},
		init: function ( dt, button, conf ) {
			var that = this;
			var col = dt.column( conf.columns );

			dt
				.on( 'column-visibility.dt'+conf.namespace, function (e, settings) {
					if ( ! settings.bDestroying ) {
						that.active( col.visible() );
					}
				} )
				.on( 'column-reorder.dt'+conf.namespace, function (e, settings, details) {
					// Don't rename buttons based on column name if the button
					// controls more than one column!
					if ( dt.columns( conf.columns ).count() !== 1 ) {
						return;
					}

					if ( typeof conf.columns === 'number' ) {
						conf.columns = details.mapping[ conf.columns ];
					}

					var col = dt.column( conf.columns );

					that.text( conf._columnText( dt, conf.columns ) );
					that.active( col.visible() );
				} );

			this.active( col.visible() );
		},
		destroy: function ( dt, button, conf ) {
			dt
				.off( 'column-visibility.dt'+conf.namespace )
				.off( 'column-reorder.dt'+conf.namespace );
		},

		_columnText: function ( dt, col ) {
			// Use DataTables' internal data structure until this is presented
			// is a public API. The other option is to use
			// `$( column(col).node() ).text()` but the node might not have been
			// populated when Buttons is constructed.
			var idx = dt.column( col ).index();
			return dt.settings()[0].aoColumns[ idx ].sTitle
				.replace(/\n/g," ")        // remove new lines
				.replace( /<.*?>/g, "" )   // strip HTML
				.replace(/^\s+|\s+$/g,""); // trim
		}
	},


	colvisRestore: {
		className: 'buttons-colvisRestore',

		text: function ( dt ) {
			return dt.i18n( 'buttons.colvisRestore', 'Restore visibility' );
		},

		init: function ( dt, button, conf ) {
			conf._visOriginal = dt.columns().indexes().map( function ( idx ) {
				return dt.column( idx ).visible();
			} ).toArray();
		},

		action: function ( e, dt, button, conf ) {
			dt.columns().every( function ( i ) {
				// Take into account that ColReorder might have disrupted our
				// indexes
				var idx = dt.colReorder && dt.colReorder.transpose ?
					dt.colReorder.transpose( i, 'toOriginal' ) :
					i;

				this.visible( conf._visOriginal[ idx ] );
			} );
		}
	},


	colvisGroup: {
		className: 'buttons-colvisGroup',

		action: function ( e, dt, button, conf ) {
			dt.columns( conf.show ).visible( true, false );
			dt.columns( conf.hide ).visible( false, false );

			dt.columns.adjust();
		},

		show: [],

		hide: []
	}
} );


return DataTable.Buttons;
}));


/*! ColReorder 1.3.2
 * ©2010-2015 SpryMedia Ltd - datatables.net/license
 */

/**
 * @summary     ColReorder
 * @description Provide the ability to reorder columns in a DataTable
 * @version     1.3.2
 * @file        dataTables.colReorder.js
 * @author      SpryMedia Ltd (www.sprymedia.co.uk)
 * @contact     www.sprymedia.co.uk/contact
 * @copyright   Copyright 2010-2014 SpryMedia Ltd.
 *
 * This source file is free software, available under the following license:
 *   MIT license - http://datatables.net/license/mit
 *
 * This source file is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
 *
 * For details please refer to: http://www.datatables.net
 */
(function( factory ){
	if ( typeof define === 'function' && define.amd ) {
		// AMD
		define( ['jquery', 'datatables.net'], function ( $ ) {
			return factory( $, window, document );
		} );
	}
	else if ( typeof exports === 'object' ) {
		// CommonJS
		module.exports = function (root, $) {
			if ( ! root ) {
				root = window;
			}

			if ( ! $ || ! $.fn.dataTable ) {
				$ = require('datatables.net')(root, $).$;
			}

			return factory( $, root, root.document );
		};
	}
	else {
		// Browser
		factory( jQuery, window, document );
	}
}(function( $, window, document, undefined ) {
'use strict';
var DataTable = $.fn.dataTable;


/**
 * Switch the key value pairing of an index array to be value key (i.e. the old value is now the
 * key). For example consider [ 2, 0, 1 ] this would be returned as [ 1, 2, 0 ].
 *  @method  fnInvertKeyValues
 *  @param   array aIn Array to switch around
 *  @returns array
 */
function fnInvertKeyValues( aIn )
{
	var aRet=[];
	for ( var i=0, iLen=aIn.length ; i<iLen ; i++ )
	{
		aRet[ aIn[i] ] = i;
	}
	return aRet;
}


/**
 * Modify an array by switching the position of two elements
 *  @method  fnArraySwitch
 *  @param   array aArray Array to consider, will be modified by reference (i.e. no return)
 *  @param   int iFrom From point
 *  @param   int iTo Insert point
 *  @returns void
 */
function fnArraySwitch( aArray, iFrom, iTo )
{
	var mStore = aArray.splice( iFrom, 1 )[0];
	aArray.splice( iTo, 0, mStore );
}


/**
 * Switch the positions of nodes in a parent node (note this is specifically designed for
 * table rows). Note this function considers all element nodes under the parent!
 *  @method  fnDomSwitch
 *  @param   string sTag Tag to consider
 *  @param   int iFrom Element to move
 *  @param   int Point to element the element to (before this point), can be null for append
 *  @returns void
 */
function fnDomSwitch( nParent, iFrom, iTo )
{
	var anTags = [];
	for ( var i=0, iLen=nParent.childNodes.length ; i<iLen ; i++ )
	{
		if ( nParent.childNodes[i].nodeType == 1 )
		{
			anTags.push( nParent.childNodes[i] );
		}
	}
	var nStore = anTags[ iFrom ];

	if ( iTo !== null )
	{
		nParent.insertBefore( nStore, anTags[iTo] );
	}
	else
	{
		nParent.appendChild( nStore );
	}
}


/**
 * Plug-in for DataTables which will reorder the internal column structure by taking the column
 * from one position (iFrom) and insert it into a given point (iTo).
 *  @method  $.fn.dataTableExt.oApi.fnColReorder
 *  @param   object oSettings DataTables settings object - automatically added by DataTables!
 *  @param   int iFrom Take the column to be repositioned from this point
 *  @param   int iTo and insert it into this point
 *  @param   bool drop Indicate if the reorder is the final one (i.e. a drop)
 *    not a live reorder
 *  @param   bool invalidateRows speeds up processing if false passed
 *  @returns void
 */
$.fn.dataTableExt.oApi.fnColReorder = function ( oSettings, iFrom, iTo, drop, invalidateRows )
{
	var i, iLen, j, jLen, jen, iCols=oSettings.aoColumns.length, nTrs, oCol;
	var attrMap = function ( obj, prop, mapping ) {
		if ( ! obj[ prop ] || typeof obj[ prop ] === 'function' ) {
			return;
		}

		var a = obj[ prop ].split('.');
		var num = a.shift();

		if ( isNaN( num*1 ) ) {
			return;
		}

		obj[ prop ] = mapping[ num*1 ]+'.'+a.join('.');
	};

	/* Sanity check in the input */
	if ( iFrom == iTo )
	{
		/* Pointless reorder */
		return;
	}

	if ( iFrom < 0 || iFrom >= iCols )
	{
		this.oApi._fnLog( oSettings, 1, "ColReorder 'from' index is out of bounds: "+iFrom );
		return;
	}

	if ( iTo < 0 || iTo >= iCols )
	{
		this.oApi._fnLog( oSettings, 1, "ColReorder 'to' index is out of bounds: "+iTo );
		return;
	}

	/*
	 * Calculate the new column array index, so we have a mapping between the old and new
	 */
	var aiMapping = [];
	for ( i=0, iLen=iCols ; i<iLen ; i++ )
	{
		aiMapping[i] = i;
	}
	fnArraySwitch( aiMapping, iFrom, iTo );
	var aiInvertMapping = fnInvertKeyValues( aiMapping );


	/*
	 * Convert all internal indexing to the new column order indexes
	 */
	/* Sorting */
	for ( i=0, iLen=oSettings.aaSorting.length ; i<iLen ; i++ )
	{
		oSettings.aaSorting[i][0] = aiInvertMapping[ oSettings.aaSorting[i][0] ];
	}

	/* Fixed sorting */
	if ( oSettings.aaSortingFixed !== null )
	{
		for ( i=0, iLen=oSettings.aaSortingFixed.length ; i<iLen ; i++ )
		{
			oSettings.aaSortingFixed[i][0] = aiInvertMapping[ oSettings.aaSortingFixed[i][0] ];
		}
	}

	/* Data column sorting (the column which the sort for a given column should take place on) */
	for ( i=0, iLen=iCols ; i<iLen ; i++ )
	{
		oCol = oSettings.aoColumns[i];
		for ( j=0, jLen=oCol.aDataSort.length ; j<jLen ; j++ )
		{
			oCol.aDataSort[j] = aiInvertMapping[ oCol.aDataSort[j] ];
		}

		// Update the column indexes
		oCol.idx = aiInvertMapping[ oCol.idx ];
	}

	// Update 1.10 optimised sort class removal variable
	$.each( oSettings.aLastSort, function (i, val) {
		oSettings.aLastSort[i].src = aiInvertMapping[ val.src ];
	} );

	/* Update the Get and Set functions for each column */
	for ( i=0, iLen=iCols ; i<iLen ; i++ )
	{
		oCol = oSettings.aoColumns[i];

		if ( typeof oCol.mData == 'number' ) {
			oCol.mData = aiInvertMapping[ oCol.mData ];
		}
		else if ( $.isPlainObject( oCol.mData ) ) {
			// HTML5 data sourced
			attrMap( oCol.mData, '_',      aiInvertMapping );
			attrMap( oCol.mData, 'filter', aiInvertMapping );
			attrMap( oCol.mData, 'sort',   aiInvertMapping );
			attrMap( oCol.mData, 'type',   aiInvertMapping );
		}
	}

	/*
	 * Move the DOM elements
	 */
	if ( oSettings.aoColumns[iFrom].bVisible )
	{
		/* Calculate the current visible index and the point to insert the node before. The insert
		 * before needs to take into account that there might not be an element to insert before,
		 * in which case it will be null, and an appendChild should be used
		 */
		var iVisibleIndex = this.oApi._fnColumnIndexToVisible( oSettings, iFrom );
		var iInsertBeforeIndex = null;

		i = iTo < iFrom ? iTo : iTo + 1;
		while ( iInsertBeforeIndex === null && i < iCols )
		{
			iInsertBeforeIndex = this.oApi._fnColumnIndexToVisible( oSettings, i );
			i++;
		}

		/* Header */
		nTrs = oSettings.nTHead.getElementsByTagName('tr');
		for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
		{
			fnDomSwitch( nTrs[i], iVisibleIndex, iInsertBeforeIndex );
		}

		/* Footer */
		if ( oSettings.nTFoot !== null )
		{
			nTrs = oSettings.nTFoot.getElementsByTagName('tr');
			for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
			{
				fnDomSwitch( nTrs[i], iVisibleIndex, iInsertBeforeIndex );
			}
		}

		/* Body */
		for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
		{
			if ( oSettings.aoData[i].nTr !== null )
			{
				fnDomSwitch( oSettings.aoData[i].nTr, iVisibleIndex, iInsertBeforeIndex );
			}
		}
	}

	/*
	 * Move the internal array elements
	 */
	/* Columns */
	fnArraySwitch( oSettings.aoColumns, iFrom, iTo );

	// regenerate the get / set functions
	for ( i=0, iLen=iCols ; i<iLen ; i++ ) {
		oSettings.oApi._fnColumnOptions( oSettings, i, {} );
	}

	/* Search columns */
	fnArraySwitch( oSettings.aoPreSearchCols, iFrom, iTo );

	/* Array array - internal data anodes cache */
	for ( i=0, iLen=oSettings.aoData.length ; i<iLen ; i++ )
	{
		var data = oSettings.aoData[i];
		var cells = data.anCells;

		if ( cells ) {
			fnArraySwitch( cells, iFrom, iTo );

			// Longer term, should this be moved into the DataTables' invalidate
			// methods?
			for ( j=0, jen=cells.length ; j<jen ; j++ ) {
				if ( cells[j] && cells[j]._DT_CellIndex ) {
					cells[j]._DT_CellIndex.column = j;
				}
			}
		}

		// For DOM sourced data, the invalidate will reread the cell into
		// the data array, but for data sources as an array, they need to
		// be flipped
		if ( data.src !== 'dom' && $.isArray( data._aData ) ) {
			fnArraySwitch( data._aData, iFrom, iTo );
		}
	}

	/* Reposition the header elements in the header layout array */
	for ( i=0, iLen=oSettings.aoHeader.length ; i<iLen ; i++ )
	{
		fnArraySwitch( oSettings.aoHeader[i], iFrom, iTo );
	}

	if ( oSettings.aoFooter !== null )
	{
		for ( i=0, iLen=oSettings.aoFooter.length ; i<iLen ; i++ )
		{
			fnArraySwitch( oSettings.aoFooter[i], iFrom, iTo );
		}
	}

	if ( invalidateRows || invalidateRows === undefined )
	{
		$.fn.dataTable.Api( oSettings ).rows().invalidate();
	}

	/*
	 * Update DataTables' event handlers
	 */

	/* Sort listener */
	for ( i=0, iLen=iCols ; i<iLen ; i++ )
	{
		$(oSettings.aoColumns[i].nTh).off('click.DT');
		this.oApi._fnSortAttachListener( oSettings, oSettings.aoColumns[i].nTh, i );
	}


	/* Fire an event so other plug-ins can update */
	$(oSettings.oInstance).trigger( 'column-reorder.dt', [ oSettings, {
		from: iFrom,
		to: iTo,
		mapping: aiInvertMapping,
		drop: drop,

		// Old style parameters for compatibility
		iFrom: iFrom,
		iTo: iTo,
		aiInvertMapping: aiInvertMapping
	} ] );
};

/**
 * ColReorder provides column visibility control for DataTables
 * @class ColReorder
 * @constructor
 * @param {object} dt DataTables settings object
 * @param {object} opts ColReorder options
 */
var ColReorder = function( dt, opts )
{
	var settings = new $.fn.dataTable.Api( dt ).settings()[0];

	// Ensure that we can't initialise on the same table twice
	if ( settings._colReorder ) {
		return settings._colReorder;
	}

	// Allow the options to be a boolean for defaults
	if ( opts === true ) {
		opts = {};
	}

	// Convert from camelCase to Hungarian, just as DataTables does
	var camelToHungarian = $.fn.dataTable.camelToHungarian;
	if ( camelToHungarian ) {
		camelToHungarian( ColReorder.defaults, ColReorder.defaults, true );
		camelToHungarian( ColReorder.defaults, opts || {} );
	}


	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Public class variables
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

	/**
	 * @namespace Settings object which contains customisable information for ColReorder instance
	 */
	this.s = {
		/**
		 * DataTables settings object
		 *  @property dt
		 *  @type     Object
		 *  @default  null
		 */
		"dt": null,

		/**
		 * Initialisation object used for this instance
		 *  @property init
		 *  @type     object
		 *  @default  {}
		 */
		"init": $.extend( true, {}, ColReorder.defaults, opts ),

		/**
		 * Number of columns to fix (not allow to be reordered)
		 *  @property fixed
		 *  @type     int
		 *  @default  0
		 */
		"fixed": 0,

		/**
		 * Number of columns to fix counting from right (not allow to be reordered)
		 *  @property fixedRight
		 *  @type     int
		 *  @default  0
		 */
		"fixedRight": 0,

		/**
		 * Callback function for once the reorder has been done
		 *  @property reorderCallback
		 *  @type     function
		 *  @default  null
		 */
		"reorderCallback": null,

		/**
		 * @namespace Information used for the mouse drag
		 */
		"mouse": {
			"startX": -1,
			"startY": -1,
			"offsetX": -1,
			"offsetY": -1,
			"target": -1,
			"targetIndex": -1,
			"fromIndex": -1
		},

		/**
		 * Information which is used for positioning the insert cusor and knowing where to do the
		 * insert. Array of objects with the properties:
		 *   x: x-axis position
		 *   to: insert point
		 *  @property aoTargets
		 *  @type     array
		 *  @default  []
		 */
		"aoTargets": []
	};


	/**
	 * @namespace Common and useful DOM elements for the class instance
	 */
	this.dom = {
		/**
		 * Dragging element (the one the mouse is moving)
		 *  @property drag
		 *  @type     element
		 *  @default  null
		 */
		"drag": null,

		/**
		 * The insert cursor
		 *  @property pointer
		 *  @type     element
		 *  @default  null
		 */
		"pointer": null
	};


	/* Constructor logic */
	this.s.dt = settings;
	this.s.dt._colReorder = this;
	this._fnConstruct();

	return this;
};



$.extend( ColReorder.prototype, {
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Public methods
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

	/**
	 * Reset the column ordering to the original ordering that was detected on
	 * start up.
	 *  @return {this} Returns `this` for chaining.
	 *
	 *  @example
	 *    // DataTables initialisation with ColReorder
	 *    var table = $('#example').dataTable( {
	 *        "sDom": 'Rlfrtip'
	 *    } );
	 *
	 *    // Add click event to a button to reset the ordering
	 *    $('#resetOrdering').click( function (e) {
	 *        e.preventDefault();
	 *        $.fn.dataTable.ColReorder( table ).fnReset();
	 *    } );
	 */
	"fnReset": function ()
	{
		this._fnOrderColumns( this.fnOrder() );

		return this;
	},

	/**
	 * `Deprecated` - Get the current order of the columns, as an array.
	 *  @return {array} Array of column identifiers
	 *  @deprecated `fnOrder` should be used in preference to this method.
	 *      `fnOrder` acts as a getter/setter.
	 */
	"fnGetCurrentOrder": function ()
	{
		return this.fnOrder();
	},

	/**
	 * Get the current order of the columns, as an array. Note that the values
	 * given in the array are unique identifiers for each column. Currently
	 * these are the original ordering of the columns that was detected on
	 * start up, but this could potentially change in future.
	 *  @return {array} Array of column identifiers
	 *
	 *  @example
	 *    // Get column ordering for the table
	 *    var order = $.fn.dataTable.ColReorder( dataTable ).fnOrder();
	 *//**
	 * Set the order of the columns, from the positions identified in the
	 * ordering array given. Note that ColReorder takes a brute force approach
	 * to reordering, so it is possible multiple reordering events will occur
	 * before the final order is settled upon.
	 *  @param {array} [set] Array of column identifiers in the new order. Note
	 *    that every column must be included, uniquely, in this array.
	 *  @return {this} Returns `this` for chaining.
	 *
	 *  @example
	 *    // Swap the first and second columns
	 *    $.fn.dataTable.ColReorder( dataTable ).fnOrder( [1, 0, 2, 3, 4] );
	 *
	 *  @example
	 *    // Move the first column to the end for the table `#example`
	 *    var curr = $.fn.dataTable.ColReorder( '#example' ).fnOrder();
	 *    var first = curr.shift();
	 *    curr.push( first );
	 *    $.fn.dataTable.ColReorder( '#example' ).fnOrder( curr );
	 *
	 *  @example
	 *    // Reverse the table's order
	 *    $.fn.dataTable.ColReorder( '#example' ).fnOrder(
	 *      $.fn.dataTable.ColReorder( '#example' ).fnOrder().reverse()
	 *    );
	 */
	"fnOrder": function ( set, original )
	{
		var a = [], i, ien, j, jen;
		var columns = this.s.dt.aoColumns;

		if ( set === undefined ){
			for ( i=0, ien=columns.length ; i<ien ; i++ ) {
				a.push( columns[i]._ColReorder_iOrigCol );
			}

			return a;
		}

		// The order given is based on the original indexes, rather than the
		// existing ones, so we need to translate from the original to current
		// before then doing the order
		if ( original ) {
			var order = this.fnOrder();

			for ( i=0, ien=set.length ; i<ien ; i++ ) {
				a.push( $.inArray( set[i], order ) );
			}

			set = a;
		}

		this._fnOrderColumns( fnInvertKeyValues( set ) );

		return this;
	},


	/**
	 * Convert from the original column index, to the original
	 *
	 * @param  {int|array} idx Index(es) to convert
	 * @param  {string} dir Transpose direction - `fromOriginal` / `toCurrent`
	 *   or `'toOriginal` / `fromCurrent`
	 * @return {int|array}     Converted values
	 */
	fnTranspose: function ( idx, dir )
	{
		if ( ! dir ) {
			dir = 'toCurrent';
		}

		var order = this.fnOrder();
		var columns = this.s.dt.aoColumns;

		if ( dir === 'toCurrent' ) {
			// Given an original index, want the current
			return ! $.isArray( idx ) ?
				$.inArray( idx, order ) :
				$.map( idx, function ( index ) {
					return $.inArray( index, order );
				} );
		}
		else {
			// Given a current index, want the original
			return ! $.isArray( idx ) ?
				columns[idx]._ColReorder_iOrigCol :
				$.map( idx, function ( index ) {
					return columns[index]._ColReorder_iOrigCol;
				} );
		}
	},


	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Private methods (they are of course public in JS, but recommended as private)
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

	/**
	 * Constructor logic
	 *  @method  _fnConstruct
	 *  @returns void
	 *  @private
	 */
	"_fnConstruct": function ()
	{
		var that = this;
		var iLen = this.s.dt.aoColumns.length;
		var table = this.s.dt.nTable;
		var i;

		/* Columns discounted from reordering - counting left to right */
		if ( this.s.init.iFixedColumns )
		{
			this.s.fixed = this.s.init.iFixedColumns;
		}

		if ( this.s.init.iFixedColumnsLeft )
		{
			this.s.fixed = this.s.init.iFixedColumnsLeft;
		}

		/* Columns discounted from reordering - counting right to left */
		this.s.fixedRight = this.s.init.iFixedColumnsRight ?
			this.s.init.iFixedColumnsRight :
			0;

		/* Drop callback initialisation option */
		if ( this.s.init.fnReorderCallback )
		{
			this.s.reorderCallback = this.s.init.fnReorderCallback;
		}

		/* Add event handlers for the drag and drop, and also mark the original column order */
		for ( i = 0; i < iLen; i++ )
		{
			if ( i > this.s.fixed-1 && i < iLen - this.s.fixedRight )
			{
				this._fnMouseListener( i, this.s.dt.aoColumns[i].nTh );
			}

			/* Mark the original column order for later reference */
			this.s.dt.aoColumns[i]._ColReorder_iOrigCol = i;
		}

		/* State saving */
		this.s.dt.oApi._fnCallbackReg( this.s.dt, 'aoStateSaveParams', function (oS, oData) {
			that._fnStateSave.call( that, oData );
		}, "ColReorder_State" );

		/* An initial column order has been specified */
		var aiOrder = null;
		if ( this.s.init.aiOrder )
		{
			aiOrder = this.s.init.aiOrder.slice();
		}

		/* State loading, overrides the column order given */
		if ( this.s.dt.oLoadedState && typeof this.s.dt.oLoadedState.ColReorder != 'undefined' &&
		  this.s.dt.oLoadedState.ColReorder.length == this.s.dt.aoColumns.length )
		{
			aiOrder = this.s.dt.oLoadedState.ColReorder;
		}

		/* If we have an order to apply - do so */
		if ( aiOrder )
		{
			/* We might be called during or after the DataTables initialisation. If before, then we need
			 * to wait until the draw is done, if after, then do what we need to do right away
			 */
			if ( !that.s.dt._bInitComplete )
			{
				var bDone = false;
				$(table).on( 'draw.dt.colReorder', function () {
					if ( !that.s.dt._bInitComplete && !bDone )
					{
						bDone = true;
						var resort = fnInvertKeyValues( aiOrder );
						that._fnOrderColumns.call( that, resort );
					}
				} );
			}
			else
			{
				var resort = fnInvertKeyValues( aiOrder );
				that._fnOrderColumns.call( that, resort );
			}
		}
		else {
			this._fnSetColumnIndexes();
		}

		// Destroy clean up
		$(table).on( 'destroy.dt.colReorder', function () {
			$(table).off( 'destroy.dt.colReorder draw.dt.colReorder' );
			$(that.s.dt.nTHead).find( '*' ).off( '.ColReorder' );

			$.each( that.s.dt.aoColumns, function (i, column) {
				$(column.nTh).removeAttr('data-column-index');
			} );

			that.s.dt._colReorder = null;
			that.s = null;
		} );
	},


	/**
	 * Set the column order from an array
	 *  @method  _fnOrderColumns
	 *  @param   array a An array of integers which dictate the column order that should be applied
	 *  @returns void
	 *  @private
	 */
	"_fnOrderColumns": function ( a )
	{
		var changed = false;

		if ( a.length != this.s.dt.aoColumns.length )
		{
			this.s.dt.oInstance.oApi._fnLog( this.s.dt, 1, "ColReorder - array reorder does not "+
				"match known number of columns. Skipping." );
			return;
		}

		for ( var i=0, iLen=a.length ; i<iLen ; i++ )
		{
			var currIndex = $.inArray( i, a );
			if ( i != currIndex )
			{
				/* Reorder our switching array */
				fnArraySwitch( a, currIndex, i );

				/* Do the column reorder in the table */
				this.s.dt.oInstance.fnColReorder( currIndex, i, true, false );

				changed = true;
			}
		}

		$.fn.dataTable.Api( this.s.dt ).rows().invalidate();

		this._fnSetColumnIndexes();

		// Has anything actually changed? If not, then nothing else to do
		if ( ! changed ) {
			return;
		}

		/* When scrolling we need to recalculate the column sizes to allow for the shift */
		if ( this.s.dt.oScroll.sX !== "" || this.s.dt.oScroll.sY !== "" )
		{
			this.s.dt.oInstance.fnAdjustColumnSizing( false );
		}

		/* Save the state */
		this.s.dt.oInstance.oApi._fnSaveState( this.s.dt );

		if ( this.s.reorderCallback !== null )
		{
			this.s.reorderCallback.call( this );
		}
	},


	/**
	 * Because we change the indexes of columns in the table, relative to their starting point
	 * we need to reorder the state columns to what they are at the starting point so we can
	 * then rearrange them again on state load!
	 *  @method  _fnStateSave
	 *  @param   object oState DataTables state
	 *  @returns string JSON encoded cookie string for DataTables
	 *  @private
	 */
	"_fnStateSave": function ( oState )
	{
		var i, iLen, aCopy, iOrigColumn;
		var oSettings = this.s.dt;
		var columns = oSettings.aoColumns;

		oState.ColReorder = [];

		/* Sorting */
		if ( oState.aaSorting ) {
			// 1.10.0-
			for ( i=0 ; i<oState.aaSorting.length ; i++ ) {
				oState.aaSorting[i][0] = columns[ oState.aaSorting[i][0] ]._ColReorder_iOrigCol;
			}

			var aSearchCopy = $.extend( true, [], oState.aoSearchCols );

			for ( i=0, iLen=columns.length ; i<iLen ; i++ )
			{
				iOrigColumn = columns[i]._ColReorder_iOrigCol;

				/* Column filter */
				oState.aoSearchCols[ iOrigColumn ] = aSearchCopy[i];

				/* Visibility */
				oState.abVisCols[ iOrigColumn ] = columns[i].bVisible;

				/* Column reordering */
				oState.ColReorder.push( iOrigColumn );
			}
		}
		else if ( oState.order ) {
			// 1.10.1+
			for ( i=0 ; i<oState.order.length ; i++ ) {
				oState.order[i][0] = columns[ oState.order[i][0] ]._ColReorder_iOrigCol;
			}

			var stateColumnsCopy = $.extend( true, [], oState.columns );

			for ( i=0, iLen=columns.length ; i<iLen ; i++ )
			{
				iOrigColumn = columns[i]._ColReorder_iOrigCol;

				/* Columns */
				oState.columns[ iOrigColumn ] = stateColumnsCopy[i];

				/* Column reordering */
				oState.ColReorder.push( iOrigColumn );
			}
		}
	},


	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Mouse drop and drag
	 */

	/**
	 * Add a mouse down listener to a particluar TH element
	 *  @method  _fnMouseListener
	 *  @param   int i Column index
	 *  @param   element nTh TH element clicked on
	 *  @returns void
	 *  @private
	 */
	"_fnMouseListener": function ( i, nTh )
	{
		var that = this;
		$(nTh).on( 'mousedown.ColReorder', function (e) {
			e.preventDefault();
			that._fnMouseDown.call( that, e, nTh );
		} );
	},


	/**
	 * Mouse down on a TH element in the table header
	 *  @method  _fnMouseDown
	 *  @param   event e Mouse event
	 *  @param   element nTh TH element to be dragged
	 *  @returns void
	 *  @private
	 */
	"_fnMouseDown": function ( e, nTh )
	{
		var that = this;

		/* Store information about the mouse position */
		var target = $(e.target).closest('th, td');
		var offset = target.offset();
		var idx = parseInt( $(nTh).attr('data-column-index'), 10 );

		if ( idx === undefined ) {
			return;
		}

		this.s.mouse.startX = e.pageX;
		this.s.mouse.startY = e.pageY;
		this.s.mouse.offsetX = e.pageX - offset.left;
		this.s.mouse.offsetY = e.pageY - offset.top;
		this.s.mouse.target = this.s.dt.aoColumns[ idx ].nTh;//target[0];
		this.s.mouse.targetIndex = idx;
		this.s.mouse.fromIndex = idx;

		this._fnRegions();

		/* Add event handlers to the document */
		$(document)
			.on( 'mousemove.ColReorder', function (e) {
				that._fnMouseMove.call( that, e );
			} )
			.on( 'mouseup.ColReorder', function (e) {
				that._fnMouseUp.call( that, e );
			} );
	},


	/**
	 * Deal with a mouse move event while dragging a node
	 *  @method  _fnMouseMove
	 *  @param   event e Mouse event
	 *  @returns void
	 *  @private
	 */
	"_fnMouseMove": function ( e )
	{
		var that = this;

		if ( this.dom.drag === null )
		{
			/* Only create the drag element if the mouse has moved a specific distance from the start
			 * point - this allows the user to make small mouse movements when sorting and not have a
			 * possibly confusing drag element showing up
			 */
			if ( Math.pow(
				Math.pow(e.pageX - this.s.mouse.startX, 2) +
				Math.pow(e.pageY - this.s.mouse.startY, 2), 0.5 ) < 5 )
			{
				return;
			}
			this._fnCreateDragNode();
		}

		/* Position the element - we respect where in the element the click occured */
		this.dom.drag.css( {
			left: e.pageX - this.s.mouse.offsetX,
			top: e.pageY - this.s.mouse.offsetY
		} );

		/* Based on the current mouse position, calculate where the insert should go */
		var bSet = false;
		var lastToIndex = this.s.mouse.toIndex;

		for ( var i=1, iLen=this.s.aoTargets.length ; i<iLen ; i++ )
		{
			if ( e.pageX < this.s.aoTargets[i-1].x + ((this.s.aoTargets[i].x-this.s.aoTargets[i-1].x)/2) )
			{
				this.dom.pointer.css( 'left', this.s.aoTargets[i-1].x );
				this.s.mouse.toIndex = this.s.aoTargets[i-1].to;
				bSet = true;
				break;
			}
		}

		// The insert element wasn't positioned in the array (less than
		// operator), so we put it at the end
		if ( !bSet )
		{
			this.dom.pointer.css( 'left', this.s.aoTargets[this.s.aoTargets.length-1].x );
			this.s.mouse.toIndex = this.s.aoTargets[this.s.aoTargets.length-1].to;
		}

		// Perform reordering if realtime updating is on and the column has moved
		if ( this.s.init.bRealtime && lastToIndex !== this.s.mouse.toIndex ) {
			this.s.dt.oInstance.fnColReorder( this.s.mouse.fromIndex, this.s.mouse.toIndex, false );
			this.s.mouse.fromIndex = this.s.mouse.toIndex;
			this._fnRegions();
		}
	},


	/**
	 * Finish off the mouse drag and insert the column where needed
	 *  @method  _fnMouseUp
	 *  @param   event e Mouse event
	 *  @returns void
	 *  @private
	 */
	"_fnMouseUp": function ( e )
	{
		var that = this;

		$(document).off( 'mousemove.ColReorder mouseup.ColReorder' );

		if ( this.dom.drag !== null )
		{
			/* Remove the guide elements */
			this.dom.drag.remove();
			this.dom.pointer.remove();
			this.dom.drag = null;
			this.dom.pointer = null;

			/* Actually do the reorder */
			this.s.dt.oInstance.fnColReorder( this.s.mouse.fromIndex, this.s.mouse.toIndex, true );
			this._fnSetColumnIndexes();

			/* When scrolling we need to recalculate the column sizes to allow for the shift */
			if ( this.s.dt.oScroll.sX !== "" || this.s.dt.oScroll.sY !== "" )
			{
				this.s.dt.oInstance.fnAdjustColumnSizing( false );
			}

			/* Save the state */
			this.s.dt.oInstance.oApi._fnSaveState( this.s.dt );

			if ( this.s.reorderCallback !== null )
			{
				this.s.reorderCallback.call( this );
			}
		}
	},


	/**
	 * Calculate a cached array with the points of the column inserts, and the
	 * 'to' points
	 *  @method  _fnRegions
	 *  @returns void
	 *  @private
	 */
	"_fnRegions": function ()
	{
		var aoColumns = this.s.dt.aoColumns;

		this.s.aoTargets.splice( 0, this.s.aoTargets.length );

		this.s.aoTargets.push( {
			"x":  $(this.s.dt.nTable).offset().left,
			"to": 0
		} );

		var iToPoint = 0;
		var total = this.s.aoTargets[0].x;

		for ( var i=0, iLen=aoColumns.length ; i<iLen ; i++ )
		{
			/* For the column / header in question, we want it's position to remain the same if the
			 * position is just to it's immediate left or right, so we only increment the counter for
			 * other columns
			 */
			if ( i != this.s.mouse.fromIndex )
			{
				iToPoint++;
			}

			if ( aoColumns[i].bVisible && aoColumns[i].nTh.style.display !=='none' )
			{
				total += $(aoColumns[i].nTh).outerWidth();

				this.s.aoTargets.push( {
					"x":  total,
					"to": iToPoint
				} );
			}
		}

		/* Disallow columns for being reordered by drag and drop, counting right to left */
		if ( this.s.fixedRight !== 0 )
		{
			this.s.aoTargets.splice( this.s.aoTargets.length - this.s.fixedRight );
		}

		/* Disallow columns for being reordered by drag and drop, counting left to right */
		if ( this.s.fixed !== 0 )
		{
			this.s.aoTargets.splice( 0, this.s.fixed );
		}
	},


	/**
	 * Copy the TH element that is being drags so the user has the idea that they are actually
	 * moving it around the page.
	 *  @method  _fnCreateDragNode
	 *  @returns void
	 *  @private
	 */
	"_fnCreateDragNode": function ()
	{
		var scrolling = this.s.dt.oScroll.sX !== "" || this.s.dt.oScroll.sY !== "";

		var origCell = this.s.dt.aoColumns[ this.s.mouse.targetIndex ].nTh;
		var origTr = origCell.parentNode;
		var origThead = origTr.parentNode;
		var origTable = origThead.parentNode;
		var cloneCell = $(origCell).clone();

		// This is a slightly odd combination of jQuery and DOM, but it is the
		// fastest and least resource intensive way I could think of cloning
		// the table with just a single header cell in it.
		this.dom.drag = $(origTable.cloneNode(false))
			.addClass( 'DTCR_clonedTable' )
			.append(
				$(origThead.cloneNode(false)).append(
					$(origTr.cloneNode(false)).append(
						cloneCell[0]
					)
				)
			)
			.css( {
				position: 'absolute',
				top: 0,
				left: 0,
				width: $(origCell).outerWidth(),
				height: $(origCell).outerHeight()
			} )
			.appendTo( 'body' );

		this.dom.pointer = $('<div></div>')
			.addClass( 'DTCR_pointer' )
			.css( {
				position: 'absolute',
				top: scrolling ?
					$('div.dataTables_scroll', this.s.dt.nTableWrapper).offset().top :
					$(this.s.dt.nTable).offset().top,
				height : scrolling ?
					$('div.dataTables_scroll', this.s.dt.nTableWrapper).height() :
					$(this.s.dt.nTable).height()
			} )
			.appendTo( 'body' );
	},


	/**
	 * Add a data attribute to the column headers, so we know the index of
	 * the row to be reordered. This allows fast detection of the index, and
	 * for this plug-in to work with FixedHeader which clones the nodes.
	 *  @private
	 */
	"_fnSetColumnIndexes": function ()
	{
		$.each( this.s.dt.aoColumns, function (i, column) {
			$(column.nTh).attr('data-column-index', i);
		} );
	}
} );





/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Static parameters
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */


/**
 * ColReorder default settings for initialisation
 *  @namespace
 *  @static
 */
ColReorder.defaults = {
	/**
	 * Predefined ordering for the columns that will be applied automatically
	 * on initialisation. If not specified then the order that the columns are
	 * found to be in the HTML is the order used.
	 *  @type array
	 *  @default null
	 *  @static
	 */
	aiOrder: null,

	/**
	 * Redraw the table's column ordering as the end user draws the column
	 * (`true`) or wait until the mouse is released (`false` - default). Note
	 * that this will perform a redraw on each reordering, which involves an
	 * Ajax request each time if you are using server-side processing in
	 * DataTables.
	 *  @type boolean
	 *  @default false
	 *  @static
	 */
	bRealtime: true,

	/**
	 * Indicate how many columns should be fixed in position (counting from the
	 * left). This will typically be 1 if used, but can be as high as you like.
	 *  @type int
	 *  @default 0
	 *  @static
	 */
	iFixedColumnsLeft: 0,

	/**
	 * As `iFixedColumnsRight` but counting from the right.
	 *  @type int
	 *  @default 0
	 *  @static
	 */
	iFixedColumnsRight: 0,

	/**
	 * Callback function that is fired when columns are reordered. The `column-
	 * reorder` event is preferred over this callback
	 *  @type function():void
	 *  @default null
	 *  @static
	 */
	fnReorderCallback: null
};



/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Constants
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/**
 * ColReorder version
 *  @constant  version
 *  @type      String
 *  @default   As code
 */
ColReorder.version = "1.3.2";



/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * DataTables interfaces
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

// Expose
$.fn.dataTable.ColReorder = ColReorder;
$.fn.DataTable.ColReorder = ColReorder;


// Register a new feature with DataTables
if ( typeof $.fn.dataTable == "function" &&
     typeof $.fn.dataTableExt.fnVersionCheck == "function" &&
     $.fn.dataTableExt.fnVersionCheck('1.10.8') )
{
	$.fn.dataTableExt.aoFeatures.push( {
		"fnInit": function( settings ) {
			var table = settings.oInstance;

			if ( ! settings._colReorder ) {
				var dtInit = settings.oInit;
				var opts = dtInit.colReorder || dtInit.oColReorder || {};

				new ColReorder( settings, opts );
			}
			else {
				table.oApi._fnLog( settings, 1, "ColReorder attempted to initialise twice. Ignoring second" );
			}

			return null; /* No node for DataTables to insert */
		},
		"cFeature": "R",
		"sFeature": "ColReorder"
	} );
}
else {
	alert( "Warning: ColReorder requires DataTables 1.10.8 or greater - www.datatables.net/download");
}


// Attach a listener to the document which listens for DataTables initialisation
// events so we can automatically initialise
$(document).on( 'preInit.dt.colReorder', function (e, settings) {
	if ( e.namespace !== 'dt' ) {
		return;
	}

	var init = settings.oInit.colReorder;
	var defaults = DataTable.defaults.colReorder;

	if ( init || defaults ) {
		var opts = $.extend( {}, init, defaults );

		if ( init !== false ) {
			new ColReorder( settings, opts  );
		}
	}
} );


// API augmentation
$.fn.dataTable.Api.register( 'colReorder.reset()', function () {
	return this.iterator( 'table', function ( ctx ) {
		ctx._colReorder.fnReset();
	} );
} );

$.fn.dataTable.Api.register( 'colReorder.order()', function ( set, original ) {
	if ( set ) {
		return this.iterator( 'table', function ( ctx ) {
			ctx._colReorder.fnOrder( set, original );
		} );
	}

	return this.context.length ?
		this.context[0]._colReorder.fnOrder() :
		null;
} );

$.fn.dataTable.Api.register( 'colReorder.transpose()', function ( idx, dir ) {
	return this.context.length && this.context[0]._colReorder ?
		this.context[0]._colReorder.fnTranspose( idx, dir ) :
		idx;
} );


return ColReorder;
}));


/*! FixedColumns 3.2.2
 * ©2010-2016 SpryMedia Ltd - datatables.net/license
 */

/**
 * @summary     FixedColumns
 * @description Freeze columns in place on a scrolling DataTable
 * @version     3.2.2
 * @file        dataTables.fixedColumns.js
 * @author      SpryMedia Ltd (www.sprymedia.co.uk)
 * @contact     www.sprymedia.co.uk/contact
 * @copyright   Copyright 2010-2016 SpryMedia Ltd.
 *
 * This source file is free software, available under the following license:
 *   MIT license - http://datatables.net/license/mit
 *
 * This source file is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
 *
 * For details please refer to: http://www.datatables.net
 */
(function( factory ){
	if ( typeof define === 'function' && define.amd ) {
		// AMD
		define( ['jquery', 'datatables.net'], function ( $ ) {
			return factory( $, window, document );
		} );
	}
	else if ( typeof exports === 'object' ) {
		// CommonJS
		module.exports = function (root, $) {
			if ( ! root ) {
				root = window;
			}

			if ( ! $ || ! $.fn.dataTable ) {
				$ = require('datatables.net')(root, $).$;
			}

			return factory( $, root, root.document );
		};
	}
	else {
		// Browser
		factory( jQuery, window, document );
	}
}(function( $, window, document, undefined ) {
'use strict';
var DataTable = $.fn.dataTable;
var _firefoxScroll;

/**
 * When making use of DataTables' x-axis scrolling feature, you may wish to
 * fix the left most column in place. This plug-in for DataTables provides
 * exactly this option (note for non-scrolling tables, please use the
 * FixedHeader plug-in, which can fix headers, footers and columns). Key
 * features include:
 *
 * * Freezes the left or right most columns to the side of the table
 * * Option to freeze two or more columns
 * * Full integration with DataTables' scrolling options
 * * Speed - FixedColumns is fast in its operation
 *
 *  @class
 *  @constructor
 *  @global
 *  @param {object} dt DataTables instance. With DataTables 1.10 this can also
 *    be a jQuery collection, a jQuery selector, DataTables API instance or
 *    settings object.
 *  @param {object} [init={}] Configuration object for FixedColumns. Options are
 *    defined by {@link FixedColumns.defaults}
 *
 *  @requires jQuery 1.7+
 *  @requires DataTables 1.8.0+
 *
 *  @example
 *      var table = $('#example').dataTable( {
 *        "scrollX": "100%"
 *      } );
 *      new $.fn.dataTable.fixedColumns( table );
 */
var FixedColumns = function ( dt, init ) {
	var that = this;

	/* Sanity check - you just know it will happen */
	if ( ! ( this instanceof FixedColumns ) ) {
		alert( "FixedColumns warning: FixedColumns must be initialised with the 'new' keyword." );
		return;
	}

	if ( init === undefined || init === true ) {
		init = {};
	}

	// Use the DataTables Hungarian notation mapping method, if it exists to
	// provide forwards compatibility for camel case variables
	var camelToHungarian = $.fn.dataTable.camelToHungarian;
	if ( camelToHungarian ) {
		camelToHungarian( FixedColumns.defaults, FixedColumns.defaults, true );
		camelToHungarian( FixedColumns.defaults, init );
	}

	// v1.10 allows the settings object to be got form a number of sources
	var dtSettings = new $.fn.dataTable.Api( dt ).settings()[0];

	/**
	 * Settings object which contains customisable information for FixedColumns instance
	 * @namespace
	 * @extends FixedColumns.defaults
	 * @private
	 */
	this.s = {
		/**
		 * DataTables settings objects
		 *  @type     object
		 *  @default  Obtained from DataTables instance
		 */
		"dt": dtSettings,

		/**
		 * Number of columns in the DataTable - stored for quick access
		 *  @type     int
		 *  @default  Obtained from DataTables instance
		 */
		"iTableColumns": dtSettings.aoColumns.length,

		/**
		 * Original outer widths of the columns as rendered by DataTables - used to calculate
		 * the FixedColumns grid bounding box
		 *  @type     array.<int>
		 *  @default  []
		 */
		"aiOuterWidths": [],

		/**
		 * Original inner widths of the columns as rendered by DataTables - used to apply widths
		 * to the columns
		 *  @type     array.<int>
		 *  @default  []
		 */
		"aiInnerWidths": [],


		/**
		 * Is the document layout right-to-left
		 * @type boolean
		 */
		rtl: $(dtSettings.nTable).css('direction') === 'rtl'
	};


	/**
	 * DOM elements used by the class instance
	 * @namespace
	 * @private
	 *
	 */
	this.dom = {
		/**
		 * DataTables scrolling element
		 *  @type     node
		 *  @default  null
		 */
		"scroller": null,

		/**
		 * DataTables header table
		 *  @type     node
		 *  @default  null
		 */
		"header": null,

		/**
		 * DataTables body table
		 *  @type     node
		 *  @default  null
		 */
		"body": null,

		/**
		 * DataTables footer table
		 *  @type     node
		 *  @default  null
		 */
		"footer": null,

		/**
		 * Display grid elements
		 * @namespace
		 */
		"grid": {
			/**
			 * Grid wrapper. This is the container element for the 3x3 grid
			 *  @type     node
			 *  @default  null
			 */
			"wrapper": null,

			/**
			 * DataTables scrolling element. This element is the DataTables
			 * component in the display grid (making up the main table - i.e.
			 * not the fixed columns).
			 *  @type     node
			 *  @default  null
			 */
			"dt": null,

			/**
			 * Left fixed column grid components
			 * @namespace
			 */
			"left": {
				"wrapper": null,
				"head": null,
				"body": null,
				"foot": null
			},

			/**
			 * Right fixed column grid components
			 * @namespace
			 */
			"right": {
				"wrapper": null,
				"head": null,
				"body": null,
				"foot": null
			}
		},

		/**
		 * Cloned table nodes
		 * @namespace
		 */
		"clone": {
			/**
			 * Left column cloned table nodes
			 * @namespace
			 */
			"left": {
				/**
				 * Cloned header table
				 *  @type     node
				 *  @default  null
				 */
				"header": null,

				/**
				 * Cloned body table
				 *  @type     node
				 *  @default  null
				 */
				"body": null,

				/**
				 * Cloned footer table
				 *  @type     node
				 *  @default  null
				 */
				"footer": null
			},

			/**
			 * Right column cloned table nodes
			 * @namespace
			 */
			"right": {
				/**
				 * Cloned header table
				 *  @type     node
				 *  @default  null
				 */
				"header": null,

				/**
				 * Cloned body table
				 *  @type     node
				 *  @default  null
				 */
				"body": null,

				/**
				 * Cloned footer table
				 *  @type     node
				 *  @default  null
				 */
				"footer": null
			}
		}
	};

	if ( dtSettings._oFixedColumns ) {
		throw 'FixedColumns already initialised on this table';
	}

	/* Attach the instance to the DataTables instance so it can be accessed easily */
	dtSettings._oFixedColumns = this;

	/* Let's do it */
	if ( ! dtSettings._bInitComplete )
	{
		dtSettings.oApi._fnCallbackReg( dtSettings, 'aoInitComplete', function () {
			that._fnConstruct( init );
		}, 'FixedColumns' );
	}
	else
	{
		this._fnConstruct( init );
	}
};



$.extend( FixedColumns.prototype , {
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Public methods
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

	/**
	 * Update the fixed columns - including headers and footers. Note that FixedColumns will
	 * automatically update the display whenever the host DataTable redraws.
	 *  @returns {void}
	 *  @example
	 *      var table = $('#example').dataTable( {
	 *          "scrollX": "100%"
	 *      } );
	 *      var fc = new $.fn.dataTable.fixedColumns( table );
	 *
	 *      // at some later point when the table has been manipulated....
	 *      fc.fnUpdate();
	 */
	"fnUpdate": function ()
	{
		this._fnDraw( true );
	},


	/**
	 * Recalculate the resizes of the 3x3 grid that FixedColumns uses for display of the table.
	 * This is useful if you update the width of the table container. Note that FixedColumns will
	 * perform this function automatically when the window.resize event is fired.
	 *  @returns {void}
	 *  @example
	 *      var table = $('#example').dataTable( {
	 *          "scrollX": "100%"
	 *      } );
	 *      var fc = new $.fn.dataTable.fixedColumns( table );
	 *
	 *      // Resize the table container and then have FixedColumns adjust its layout....
	 *      $('#content').width( 1200 );
	 *      fc.fnRedrawLayout();
	 */
	"fnRedrawLayout": function ()
	{
		this._fnColCalc();
		this._fnGridLayout();
		this.fnUpdate();
	},


	/**
	 * Mark a row such that it's height should be recalculated when using 'semiauto' row
	 * height matching. This function will have no effect when 'none' or 'auto' row height
	 * matching is used.
	 *  @param   {Node} nTr TR element that should have it's height recalculated
	 *  @returns {void}
	 *  @example
	 *      var table = $('#example').dataTable( {
	 *          "scrollX": "100%"
	 *      } );
	 *      var fc = new $.fn.dataTable.fixedColumns( table );
	 *
	 *      // manipulate the table - mark the row as needing an update then update the table
	 *      // this allows the redraw performed by DataTables fnUpdate to recalculate the row
	 *      // height
	 *      fc.fnRecalculateHeight();
	 *      table.fnUpdate( $('#example tbody tr:eq(0)')[0], ["insert date", 1, 2, 3 ... ]);
	 */
	"fnRecalculateHeight": function ( nTr )
	{
		delete nTr._DTTC_iHeight;
		nTr.style.height = 'auto';
	},


	/**
	 * Set the height of a given row - provides cross browser compatibility
	 *  @param   {Node} nTarget TR element that should have it's height recalculated
	 *  @param   {int} iHeight Height in pixels to set
	 *  @returns {void}
	 *  @example
	 *      var table = $('#example').dataTable( {
	 *          "scrollX": "100%"
	 *      } );
	 *      var fc = new $.fn.dataTable.fixedColumns( table );
	 *
	 *      // You may want to do this after manipulating a row in the fixed column
	 *      fc.fnSetRowHeight( $('#example tbody tr:eq(0)')[0], 50 );
	 */
	"fnSetRowHeight": function ( nTarget, iHeight )
	{
		nTarget.style.height = iHeight+"px";
	},


	/**
	 * Get data index information about a row or cell in the table body.
	 * This function is functionally identical to fnGetPosition in DataTables,
	 * taking the same parameter (TH, TD or TR node) and returning exactly the
	 * the same information (data index information). THe difference between
	 * the two is that this method takes into account the fixed columns in the
	 * table, so you can pass in nodes from the master table, or the cloned
	 * tables and get the index position for the data in the main table.
	 *  @param {node} node TR, TH or TD element to get the information about
	 *  @returns {int} If nNode is given as a TR, then a single index is 
	 *    returned, or if given as a cell, an array of [row index, column index
	 *    (visible), column index (all)] is given.
	 */
	"fnGetPosition": function ( node )
	{
		var idx;
		var inst = this.s.dt.oInstance;

		if ( ! $(node).parents('.DTFC_Cloned').length )
		{
			// Not in a cloned table
			return inst.fnGetPosition( node );
		}
		else
		{
			// Its in the cloned table, so need to look up position
			if ( node.nodeName.toLowerCase() === 'tr' ) {
				idx = $(node).index();
				return inst.fnGetPosition( $('tr', this.s.dt.nTBody)[ idx ] );
			}
			else
			{
				var colIdx = $(node).index();
				idx = $(node.parentNode).index();
				var row = inst.fnGetPosition( $('tr', this.s.dt.nTBody)[ idx ] );

				return [
					row,
					colIdx,
					inst.oApi._fnVisibleToColumnIndex( this.s.dt, colIdx )
				];
			}
		}
	},



	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Private methods (they are of course public in JS, but recommended as private)
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

	/**
	 * Initialisation for FixedColumns
	 *  @param   {Object} oInit User settings for initialisation
	 *  @returns {void}
	 *  @private
	 */
	"_fnConstruct": function ( oInit )
	{
		var i, iLen, iWidth,
			that = this;

		/* Sanity checking */
		if ( typeof this.s.dt.oInstance.fnVersionCheck != 'function' ||
		     this.s.dt.oInstance.fnVersionCheck( '1.8.0' ) !== true )
		{
			alert( "FixedColumns "+FixedColumns.VERSION+" required DataTables 1.8.0 or later. "+
				"Please upgrade your DataTables installation" );
			return;
		}

		if ( this.s.dt.oScroll.sX === "" )
		{
			this.s.dt.oInstance.oApi._fnLog( this.s.dt, 1, "FixedColumns is not needed (no "+
				"x-scrolling in DataTables enabled), so no action will be taken. Use 'FixedHeader' for "+
				"column fixing when scrolling is not enabled" );
			return;
		}

		/* Apply the settings from the user / defaults */
		this.s = $.extend( true, this.s, FixedColumns.defaults, oInit );

		/* Set up the DOM as we need it and cache nodes */
		var classes = this.s.dt.oClasses;
		this.dom.grid.dt = $(this.s.dt.nTable).parents('div.'+classes.sScrollWrapper)[0];
		this.dom.scroller = $('div.'+classes.sScrollBody, this.dom.grid.dt )[0];

		/* Set up the DOM that we want for the fixed column layout grid */
		this._fnColCalc();
		this._fnGridSetup();

		/* Event handlers */
		var mouseController;
		var mouseDown = false;

		// When the mouse is down (drag scroll) the mouse controller cannot
		// change, as the browser keeps the original element as the scrolling one
		$(this.s.dt.nTableWrapper).on( 'mousedown.DTFC', function () {
			mouseDown = true;

			$(document).one( 'mouseup', function () {
				mouseDown = false;
			} );
		} );

		// When the body is scrolled - scroll the left and right columns
		$(this.dom.scroller)
			.on( 'mouseover.DTFC touchstart.DTFC', function () {
				if ( ! mouseDown ) {
					mouseController = 'main';
				}
			} )
			.on( 'scroll.DTFC', function (e) {
				if ( ! mouseController && e.originalEvent ) {
					mouseController = 'main';
				}

				if ( mouseController === 'main' ) {
					if ( that.s.iLeftColumns > 0 ) {
						that.dom.grid.left.liner.scrollTop = that.dom.scroller.scrollTop;
					}
					if ( that.s.iRightColumns > 0 ) {
						that.dom.grid.right.liner.scrollTop = that.dom.scroller.scrollTop;
					}
				}
			} );

		var wheelType = 'onwheel' in document.createElement('div') ?
			'wheel.DTFC' :
			'mousewheel.DTFC';

		if ( that.s.iLeftColumns > 0 ) {
			// When scrolling the left column, scroll the body and right column
			$(that.dom.grid.left.liner)
				.on( 'mouseover.DTFC touchstart.DTFC', function () {
					if ( ! mouseDown ) {
						mouseController = 'left';
					}
				} )
				.on( 'scroll.DTFC', function ( e ) {
					if ( ! mouseController && e.originalEvent ) {
						mouseController = 'left';
					}

					if ( mouseController === 'left' ) {
						that.dom.scroller.scrollTop = that.dom.grid.left.liner.scrollTop;
						if ( that.s.iRightColumns > 0 ) {
							that.dom.grid.right.liner.scrollTop = that.dom.grid.left.liner.scrollTop;
						}
					}
				} )
				.on( wheelType, function(e) {
					// Pass horizontal scrolling through
					var xDelta = e.type === 'wheel' ?
						-e.originalEvent.deltaX :
						e.originalEvent.wheelDeltaX;
					that.dom.scroller.scrollLeft -= xDelta;
				} );
		}

		if ( that.s.iRightColumns > 0 ) {
			// When scrolling the right column, scroll the body and the left column
			$(that.dom.grid.right.liner)
				.on( 'mouseover.DTFC touchstart.DTFC', function () {
					if ( ! mouseDown ) {
						mouseController = 'right';
					}
				} )
				.on( 'scroll.DTFC', function ( e ) {
					if ( ! mouseController && e.originalEvent ) {
						mouseController = 'right';
					}

					if ( mouseController === 'right' ) {
						that.dom.scroller.scrollTop = that.dom.grid.right.liner.scrollTop;
						if ( that.s.iLeftColumns > 0 ) {
							that.dom.grid.left.liner.scrollTop = that.dom.grid.right.liner.scrollTop;
						}
					}
				} )
				.on( wheelType, function(e) {
					// Pass horizontal scrolling through
					var xDelta = e.type === 'wheel' ?
						-e.originalEvent.deltaX :
						e.originalEvent.wheelDeltaX;
					that.dom.scroller.scrollLeft -= xDelta;
				} );
		}

		$(window).on( 'resize.DTFC', function () {
			that._fnGridLayout.call( that );
		} );

		var bFirstDraw = true;
		var jqTable = $(this.s.dt.nTable);

		jqTable
			.on( 'draw.dt.DTFC', function () {
				that._fnColCalc();
				that._fnDraw.call( that, bFirstDraw );
				bFirstDraw = false;
			} )
			.on( 'column-sizing.dt.DTFC', function () {
				that._fnColCalc();
				that._fnGridLayout( that );
			} )
			.on( 'column-visibility.dt.DTFC', function ( e, settings, column, vis, recalc ) {
				if ( recalc === undefined || recalc ) {
					that._fnColCalc();
					that._fnGridLayout( that );
					that._fnDraw( true );
				}
			} )
			.on( 'select.dt.DTFC deselect.dt.DTFC', function ( e, dt, type, indexes ) {
				if ( e.namespace === 'dt' ) {
					that._fnDraw( false );
				}
			} )
			.on( 'destroy.dt.DTFC', function () {
				jqTable.off( '.DTFC' );

				$(that.dom.scroller).off( '.DTFC' );
				$(window).off( '.DTFC' );
				$(that.s.dt.nTableWrapper).off( '.DTFC' );

				$(that.dom.grid.left.liner).off( '.DTFC '+wheelType );
				$(that.dom.grid.left.wrapper).remove();

				$(that.dom.grid.right.liner).off( '.DTFC '+wheelType );
				$(that.dom.grid.right.wrapper).remove();
			} );

		/* Get things right to start with - note that due to adjusting the columns, there must be
		 * another redraw of the main table. It doesn't need to be a full redraw however.
		 */
		this._fnGridLayout();
		this.s.dt.oInstance.fnDraw(false);
	},


	/**
	 * Calculate the column widths for the grid layout
	 *  @returns {void}
	 *  @private
	 */
	"_fnColCalc": function ()
	{
		var that = this;
		var iLeftWidth = 0;
		var iRightWidth = 0;

		this.s.aiInnerWidths = [];
		this.s.aiOuterWidths = [];

		$.each( this.s.dt.aoColumns, function (i, col) {
			var th = $(col.nTh);
			var border;

			if ( ! th.filter(':visible').length ) {
				that.s.aiInnerWidths.push( 0 );
				that.s.aiOuterWidths.push( 0 );
			}
			else
			{
				// Inner width is used to assign widths to cells
				// Outer width is used to calculate the container
				var iWidth = th.outerWidth();

				// When working with the left most-cell, need to add on the
				// table's border to the outerWidth, since we need to take
				// account of it, but it isn't in any cell
				if ( that.s.aiOuterWidths.length === 0 ) {
					border = $(that.s.dt.nTable).css('border-left-width');
					iWidth += typeof border === 'string' ? 1 : parseInt( border, 10 );
				}

				// Likewise with the final column on the right
				if ( that.s.aiOuterWidths.length === that.s.dt.aoColumns.length-1 ) {
					border = $(that.s.dt.nTable).css('border-right-width');
					iWidth += typeof border === 'string' ? 1 : parseInt( border, 10 );
				}

				that.s.aiOuterWidths.push( iWidth );
				that.s.aiInnerWidths.push( th.width() );

				if ( i < that.s.iLeftColumns )
				{
					iLeftWidth += iWidth;
				}

				if ( that.s.iTableColumns-that.s.iRightColumns <= i )
				{
					iRightWidth += iWidth;
				}
			}
		} );

		this.s.iLeftWidth = iLeftWidth;
		this.s.iRightWidth = iRightWidth;
	},


	/**
	 * Set up the DOM for the fixed column. The way the layout works is to create a 1x3 grid
	 * for the left column, the DataTable (for which we just reuse the scrolling element DataTable
	 * puts into the DOM) and the right column. In each of he two fixed column elements there is a
	 * grouping wrapper element and then a head, body and footer wrapper. In each of these we then
	 * place the cloned header, body or footer tables. This effectively gives as 3x3 grid structure.
	 *  @returns {void}
	 *  @private
	 */
	"_fnGridSetup": function ()
	{
		var that = this;
		var oOverflow = this._fnDTOverflow();
		var block;

		this.dom.body = this.s.dt.nTable;
		this.dom.header = this.s.dt.nTHead.parentNode;
		this.dom.header.parentNode.parentNode.style.position = "relative";

		var nSWrapper =
			$('<div class="DTFC_ScrollWrapper" style="position:relative; clear:both;">'+
				'<div class="DTFC_LeftWrapper" style="position:absolute; top:0; left:0;">'+
					'<div class="DTFC_LeftHeadWrapper" style="position:relative; top:0; left:0; overflow:hidden;"></div>'+
					'<div class="DTFC_LeftBodyWrapper" style="position:relative; top:0; left:0; overflow:hidden;">'+
						'<div class="DTFC_LeftBodyLiner" style="position:relative; top:0; left:0; overflow-y:scroll;"></div>'+
					'</div>'+
					'<div class="DTFC_LeftFootWrapper" style="position:relative; top:0; left:0; overflow:hidden;"></div>'+
				'</div>'+
				'<div class="DTFC_RightWrapper" style="position:absolute; top:0; right:0;">'+
					'<div class="DTFC_RightHeadWrapper" style="position:relative; top:0; left:0;">'+
						'<div class="DTFC_RightHeadBlocker DTFC_Blocker" style="position:absolute; top:0; bottom:0;"></div>'+
					'</div>'+
					'<div class="DTFC_RightBodyWrapper" style="position:relative; top:0; left:0; overflow:hidden;">'+
						'<div class="DTFC_RightBodyLiner" style="position:relative; top:0; left:0; overflow-y:scroll;"></div>'+
					'</div>'+
					'<div class="DTFC_RightFootWrapper" style="position:relative; top:0; left:0;">'+
						'<div class="DTFC_RightFootBlocker DTFC_Blocker" style="position:absolute; top:0; bottom:0;"></div>'+
					'</div>'+
				'</div>'+
			'</div>')[0];
		var nLeft = nSWrapper.childNodes[0];
		var nRight = nSWrapper.childNodes[1];

		this.dom.grid.dt.parentNode.insertBefore( nSWrapper, this.dom.grid.dt );
		nSWrapper.appendChild( this.dom.grid.dt );

		this.dom.grid.wrapper = nSWrapper;

		if ( this.s.iLeftColumns > 0 )
		{
			this.dom.grid.left.wrapper = nLeft;
			this.dom.grid.left.head = nLeft.childNodes[0];
			this.dom.grid.left.body = nLeft.childNodes[1];
			this.dom.grid.left.liner = $('div.DTFC_LeftBodyLiner', nSWrapper)[0];

			nSWrapper.appendChild( nLeft );
		}

		if ( this.s.iRightColumns > 0 )
		{
			this.dom.grid.right.wrapper = nRight;
			this.dom.grid.right.head = nRight.childNodes[0];
			this.dom.grid.right.body = nRight.childNodes[1];
			this.dom.grid.right.liner = $('div.DTFC_RightBodyLiner', nSWrapper)[0];

			nRight.style.right = oOverflow.bar+"px";

			block = $('div.DTFC_RightHeadBlocker', nSWrapper)[0];
			block.style.width = oOverflow.bar+"px";
			block.style.right = -oOverflow.bar+"px";
			this.dom.grid.right.headBlock = block;

			block = $('div.DTFC_RightFootBlocker', nSWrapper)[0];
			block.style.width = oOverflow.bar+"px";
			block.style.right = -oOverflow.bar+"px";
			this.dom.grid.right.footBlock = block;

			nSWrapper.appendChild( nRight );
		}

		if ( this.s.dt.nTFoot )
		{
			this.dom.footer = this.s.dt.nTFoot.parentNode;
			if ( this.s.iLeftColumns > 0 )
			{
				this.dom.grid.left.foot = nLeft.childNodes[2];
			}
			if ( this.s.iRightColumns > 0 )
			{
				this.dom.grid.right.foot = nRight.childNodes[2];
			}
		}

		// RTL support - swap the position of the left and right columns (#48)
		if ( this.s.rtl ) {
			$('div.DTFC_RightHeadBlocker', nSWrapper).css( {
				left: -oOverflow.bar+'px',
				right: ''
			} );
		}
	},


	/**
	 * Style and position the grid used for the FixedColumns layout
	 *  @returns {void}
	 *  @private
	 */
	"_fnGridLayout": function ()
	{
		var that = this;
		var oGrid = this.dom.grid;
		var iWidth = $(oGrid.wrapper).width();
		var iBodyHeight = $(this.s.dt.nTable.parentNode).outerHeight();
		var iFullHeight = $(this.s.dt.nTable.parentNode.parentNode).outerHeight();
		var oOverflow = this._fnDTOverflow();
		var iLeftWidth = this.s.iLeftWidth;
		var iRightWidth = this.s.iRightWidth;
		var rtl = $(this.dom.body).css('direction') === 'rtl';
		var wrapper;
		var scrollbarAdjust = function ( node, width ) {
			if ( ! oOverflow.bar ) {
				// If there is no scrollbar (Macs) we need to hide the auto scrollbar
				node.style.width = (width+20)+"px";
				node.style.paddingRight = "20px";
				node.style.boxSizing = "border-box";
			}
			else if ( that._firefoxScrollError() ) {
				// See the above function for why this is required
				if ( $(node).height() > 34 ) {
					node.style.width = (width+oOverflow.bar)+"px";
				}
			}
			else {
				// Otherwise just overflow by the scrollbar
				node.style.width = (width+oOverflow.bar)+"px";
			}
		};

		// When x scrolling - don't paint the fixed columns over the x scrollbar
		if ( oOverflow.x )
		{
			iBodyHeight -= oOverflow.bar;
		}

		oGrid.wrapper.style.height = iFullHeight+"px";

		if ( this.s.iLeftColumns > 0 )
		{
			wrapper = oGrid.left.wrapper;
			wrapper.style.width = iLeftWidth+'px';
			wrapper.style.height = '1px';

			// Swap the position of the left and right columns for rtl (#48)
			// This is always up against the edge, scrollbar on the far side
			if ( rtl ) {
				wrapper.style.left = '';
				wrapper.style.right = 0;
			}
			else {
				wrapper.style.left = 0;
				wrapper.style.right = '';
			}

			oGrid.left.body.style.height = iBodyHeight+"px";
			if ( oGrid.left.foot ) {
				oGrid.left.foot.style.top = (oOverflow.x ? oOverflow.bar : 0)+"px"; // shift footer for scrollbar
			}

			scrollbarAdjust( oGrid.left.liner, iLeftWidth );
			oGrid.left.liner.style.height = iBodyHeight+"px";
		}

		if ( this.s.iRightColumns > 0 )
		{
			wrapper = oGrid.right.wrapper;
			wrapper.style.width = iRightWidth+'px';
			wrapper.style.height = '1px';

			// Need to take account of the vertical scrollbar
			if ( this.s.rtl ) {
				wrapper.style.left = oOverflow.y ? oOverflow.bar+'px' : 0;
				wrapper.style.right = '';
			}
			else {
				wrapper.style.left = '';
				wrapper.style.right = oOverflow.y ? oOverflow.bar+'px' : 0;
			}

			oGrid.right.body.style.height = iBodyHeight+"px";
			if ( oGrid.right.foot ) {
				oGrid.right.foot.style.top = (oOverflow.x ? oOverflow.bar : 0)+"px";
			}

			scrollbarAdjust( oGrid.right.liner, iRightWidth );
			oGrid.right.liner.style.height = iBodyHeight+"px";

			oGrid.right.headBlock.style.display = oOverflow.y ? 'block' : 'none';
			oGrid.right.footBlock.style.display = oOverflow.y ? 'block' : 'none';
		}
	},


	/**
	 * Get information about the DataTable's scrolling state - specifically if the table is scrolling
	 * on either the x or y axis, and also the scrollbar width.
	 *  @returns {object} Information about the DataTables scrolling state with the properties:
	 *    'x', 'y' and 'bar'
	 *  @private
	 */
	"_fnDTOverflow": function ()
	{
		var nTable = this.s.dt.nTable;
		var nTableScrollBody = nTable.parentNode;
		var out = {
			"x": false,
			"y": false,
			"bar": this.s.dt.oScroll.iBarWidth
		};

		if ( nTable.offsetWidth > nTableScrollBody.clientWidth )
		{
			out.x = true;
		}

		if ( nTable.offsetHeight > nTableScrollBody.clientHeight )
		{
			out.y = true;
		}

		return out;
	},


	/**
	 * Clone and position the fixed columns
	 *  @returns {void}
	 *  @param   {Boolean} bAll Indicate if the header and footer should be updated as well (true)
	 *  @private
	 */
	"_fnDraw": function ( bAll )
	{
		this._fnGridLayout();
		this._fnCloneLeft( bAll );
		this._fnCloneRight( bAll );

		/* Draw callback function */
		if ( this.s.fnDrawCallback !== null )
		{
			this.s.fnDrawCallback.call( this, this.dom.clone.left, this.dom.clone.right );
		}

		/* Event triggering */
		$(this).trigger( 'draw.dtfc', {
			"leftClone": this.dom.clone.left,
			"rightClone": this.dom.clone.right
		} );
	},


	/**
	 * Clone the right columns
	 *  @returns {void}
	 *  @param   {Boolean} bAll Indicate if the header and footer should be updated as well (true)
	 *  @private
	 */
	"_fnCloneRight": function ( bAll )
	{
		if ( this.s.iRightColumns <= 0 ) {
			return;
		}

		var that = this,
			i, jq,
			aiColumns = [];

		for ( i=this.s.iTableColumns-this.s.iRightColumns ; i<this.s.iTableColumns ; i++ ) {
			if ( this.s.dt.aoColumns[i].bVisible ) {
				aiColumns.push( i );
			}
		}

		this._fnClone( this.dom.clone.right, this.dom.grid.right, aiColumns, bAll );
	},


	/**
	 * Clone the left columns
	 *  @returns {void}
	 *  @param   {Boolean} bAll Indicate if the header and footer should be updated as well (true)
	 *  @private
	 */
	"_fnCloneLeft": function ( bAll )
	{
		if ( this.s.iLeftColumns <= 0 ) {
			return;
		}

		var that = this,
			i, jq,
			aiColumns = [];

		for ( i=0 ; i<this.s.iLeftColumns ; i++ ) {
			if ( this.s.dt.aoColumns[i].bVisible ) {
				aiColumns.push( i );
			}
		}

		this._fnClone( this.dom.clone.left, this.dom.grid.left, aiColumns, bAll );
	},


	/**
	 * Make a copy of the layout object for a header or footer element from DataTables. Note that
	 * this method will clone the nodes in the layout object.
	 *  @returns {Array} Copy of the layout array
	 *  @param   {Object} aoOriginal Layout array from DataTables (aoHeader or aoFooter)
	 *  @param   {Object} aiColumns Columns to copy
	 *  @param   {boolean} events Copy cell events or not
	 *  @private
	 */
	"_fnCopyLayout": function ( aoOriginal, aiColumns, events )
	{
		var aReturn = [];
		var aClones = [];
		var aCloned = [];

		for ( var i=0, iLen=aoOriginal.length ; i<iLen ; i++ )
		{
			var aRow = [];
			aRow.nTr = $(aoOriginal[i].nTr).clone(events, false)[0];

			for ( var j=0, jLen=this.s.iTableColumns ; j<jLen ; j++ )
			{
				if ( $.inArray( j, aiColumns ) === -1 )
				{
					continue;
				}

				var iCloned = $.inArray( aoOriginal[i][j].cell, aCloned );
				if ( iCloned === -1 )
				{
					var nClone = $(aoOriginal[i][j].cell).clone(events, false)[0];
					aClones.push( nClone );
					aCloned.push( aoOriginal[i][j].cell );

					aRow.push( {
						"cell": nClone,
						"unique": aoOriginal[i][j].unique
					} );
				}
				else
				{
					aRow.push( {
						"cell": aClones[ iCloned ],
						"unique": aoOriginal[i][j].unique
					} );
				}
			}

			aReturn.push( aRow );
		}

		return aReturn;
	},


	/**
	 * Clone the DataTable nodes and place them in the DOM (sized correctly)
	 *  @returns {void}
	 *  @param   {Object} oClone Object containing the header, footer and body cloned DOM elements
	 *  @param   {Object} oGrid Grid object containing the display grid elements for the cloned
	 *                    column (left or right)
	 *  @param   {Array} aiColumns Column indexes which should be operated on from the DataTable
	 *  @param   {Boolean} bAll Indicate if the header and footer should be updated as well (true)
	 *  @private
	 */
	"_fnClone": function ( oClone, oGrid, aiColumns, bAll )
	{
		var that = this,
			i, iLen, j, jLen, jq, nTarget, iColumn, nClone, iIndex, aoCloneLayout,
			jqCloneThead, aoFixedHeader,
			dt = this.s.dt;

		/*
		 * Header
		 */
		if ( bAll )
		{
			$(oClone.header).remove();

			oClone.header = $(this.dom.header).clone(true, false)[0];
			oClone.header.className += " DTFC_Cloned";
			oClone.header.style.width = "100%";
			oGrid.head.appendChild( oClone.header );

			/* Copy the DataTables layout cache for the header for our floating column */
			aoCloneLayout = this._fnCopyLayout( dt.aoHeader, aiColumns, true );
			jqCloneThead = $('>thead', oClone.header);
			jqCloneThead.empty();

			/* Add the created cloned TR elements to the table */
			for ( i=0, iLen=aoCloneLayout.length ; i<iLen ; i++ )
			{
				jqCloneThead[0].appendChild( aoCloneLayout[i].nTr );
			}

			/* Use the handy _fnDrawHead function in DataTables to do the rowspan/colspan
			 * calculations for us
			 */
			dt.oApi._fnDrawHead( dt, aoCloneLayout, true );
		}
		else
		{
			/* To ensure that we copy cell classes exactly, regardless of colspan, multiple rows
			 * etc, we make a copy of the header from the DataTable again, but don't insert the
			 * cloned cells, just copy the classes across. To get the matching layout for the
			 * fixed component, we use the DataTables _fnDetectHeader method, allowing 1:1 mapping
			 */
			aoCloneLayout = this._fnCopyLayout( dt.aoHeader, aiColumns, false );
			aoFixedHeader=[];

			dt.oApi._fnDetectHeader( aoFixedHeader, $('>thead', oClone.header)[0] );

			for ( i=0, iLen=aoCloneLayout.length ; i<iLen ; i++ )
			{
				for ( j=0, jLen=aoCloneLayout[i].length ; j<jLen ; j++ )
				{
					aoFixedHeader[i][j].cell.className = aoCloneLayout[i][j].cell.className;

					// If jQuery UI theming is used we need to copy those elements as well
					$('span.DataTables_sort_icon', aoFixedHeader[i][j].cell).each( function () {
						this.className = $('span.DataTables_sort_icon', aoCloneLayout[i][j].cell)[0].className;
					} );
				}
			}
		}
		this._fnEqualiseHeights( 'thead', this.dom.header, oClone.header );

		/*
		 * Body
		 */
		if ( this.s.sHeightMatch == 'auto' )
		{
			/* Remove any heights which have been applied already and let the browser figure it out */
			$('>tbody>tr', that.dom.body).css('height', 'auto');
		}

		if ( oClone.body !== null )
		{
			$(oClone.body).remove();
			oClone.body = null;
		}

		oClone.body = $(this.dom.body).clone(true)[0];
		oClone.body.className += " DTFC_Cloned";
		oClone.body.style.paddingBottom = dt.oScroll.iBarWidth+"px";
		oClone.body.style.marginBottom = (dt.oScroll.iBarWidth*2)+"px"; /* For IE */
		if ( oClone.body.getAttribute('id') !== null )
		{
			oClone.body.removeAttribute('id');
		}

		$('>thead>tr', oClone.body).empty();
		$('>tfoot', oClone.body).remove();

		var nBody = $('tbody', oClone.body)[0];
		$(nBody).empty();
		if ( dt.aiDisplay.length > 0 )
		{
			/* Copy the DataTables' header elements to force the column width in exactly the
			 * same way that DataTables does it - have the header element, apply the width and
			 * colapse it down
			 */
			var nInnerThead = $('>thead>tr', oClone.body)[0];
			for ( iIndex=0 ; iIndex<aiColumns.length ; iIndex++ )
			{
				iColumn = aiColumns[iIndex];

				nClone = $(dt.aoColumns[iColumn].nTh).clone(true)[0];
				nClone.innerHTML = "";

				var oStyle = nClone.style;
				oStyle.paddingTop = "0";
				oStyle.paddingBottom = "0";
				oStyle.borderTopWidth = "0";
				oStyle.borderBottomWidth = "0";
				oStyle.height = 0;
				oStyle.width = that.s.aiInnerWidths[iColumn]+"px";

				nInnerThead.appendChild( nClone );
			}

			/* Add in the tbody elements, cloning form the master table */
			$('>tbody>tr', that.dom.body).each( function (z) {
				var i = that.s.dt.oFeatures.bServerSide===false ?
					that.s.dt.aiDisplay[ that.s.dt._iDisplayStart+z ] : z;
				var aTds = that.s.dt.aoData[ i ].anCells || $(this).children('td, th');

				var n = this.cloneNode(false);
				n.removeAttribute('id');
				n.setAttribute( 'data-dt-row', i );

				for ( iIndex=0 ; iIndex<aiColumns.length ; iIndex++ )
				{
					iColumn = aiColumns[iIndex];

					if ( aTds.length > 0 )
					{
						nClone = $( aTds[iColumn] ).clone(true, true)[0];
						nClone.setAttribute( 'data-dt-row', i );
						nClone.setAttribute( 'data-dt-column', iIndex );
						n.appendChild( nClone );
					}
				}
				nBody.appendChild( n );
			} );
		}
		else
		{
			$('>tbody>tr', that.dom.body).each( function (z) {
				nClone = this.cloneNode(true);
				nClone.className += ' DTFC_NoData';
				$('td', nClone).html('');
				nBody.appendChild( nClone );
			} );
		}

		oClone.body.style.width = "100%";
		oClone.body.style.margin = "0";
		oClone.body.style.padding = "0";

		// Interop with Scroller - need to use a height forcing element in the
		// scrolling area in the same way that Scroller does in the body scroll.
		if ( dt.oScroller !== undefined )
		{
			var scrollerForcer = dt.oScroller.dom.force;

			if ( ! oGrid.forcer ) {
				oGrid.forcer = scrollerForcer.cloneNode( true );
				oGrid.liner.appendChild( oGrid.forcer );
			}
			else {
				oGrid.forcer.style.height = scrollerForcer.style.height;
			}
		}

		oGrid.liner.appendChild( oClone.body );

		this._fnEqualiseHeights( 'tbody', that.dom.body, oClone.body );

		/*
		 * Footer
		 */
		if ( dt.nTFoot !== null )
		{
			if ( bAll )
			{
				if ( oClone.footer !== null )
				{
					oClone.footer.parentNode.removeChild( oClone.footer );
				}
				oClone.footer = $(this.dom.footer).clone(true, true)[0];
				oClone.footer.className += " DTFC_Cloned";
				oClone.footer.style.width = "100%";
				oGrid.foot.appendChild( oClone.footer );

				/* Copy the footer just like we do for the header */
				aoCloneLayout = this._fnCopyLayout( dt.aoFooter, aiColumns, true );
				var jqCloneTfoot = $('>tfoot', oClone.footer);
				jqCloneTfoot.empty();

				for ( i=0, iLen=aoCloneLayout.length ; i<iLen ; i++ )
				{
					jqCloneTfoot[0].appendChild( aoCloneLayout[i].nTr );
				}
				dt.oApi._fnDrawHead( dt, aoCloneLayout, true );
			}
			else
			{
				aoCloneLayout = this._fnCopyLayout( dt.aoFooter, aiColumns, false );
				var aoCurrFooter=[];

				dt.oApi._fnDetectHeader( aoCurrFooter, $('>tfoot', oClone.footer)[0] );

				for ( i=0, iLen=aoCloneLayout.length ; i<iLen ; i++ )
				{
					for ( j=0, jLen=aoCloneLayout[i].length ; j<jLen ; j++ )
					{
						aoCurrFooter[i][j].cell.className = aoCloneLayout[i][j].cell.className;
					}
				}
			}
			this._fnEqualiseHeights( 'tfoot', this.dom.footer, oClone.footer );
		}

		/* Equalise the column widths between the header footer and body - body get's priority */
		var anUnique = dt.oApi._fnGetUniqueThs( dt, $('>thead', oClone.header)[0] );
		$(anUnique).each( function (i) {
			iColumn = aiColumns[i];
			this.style.width = that.s.aiInnerWidths[iColumn]+"px";
		} );

		if ( that.s.dt.nTFoot !== null )
		{
			anUnique = dt.oApi._fnGetUniqueThs( dt, $('>tfoot', oClone.footer)[0] );
			$(anUnique).each( function (i) {
				iColumn = aiColumns[i];
				this.style.width = that.s.aiInnerWidths[iColumn]+"px";
			} );
		}
	},


	/**
	 * From a given table node (THEAD etc), get a list of TR direct child elements
	 *  @param   {Node} nIn Table element to search for TR elements (THEAD, TBODY or TFOOT element)
	 *  @returns {Array} List of TR elements found
	 *  @private
	 */
	"_fnGetTrNodes": function ( nIn )
	{
		var aOut = [];
		for ( var i=0, iLen=nIn.childNodes.length ; i<iLen ; i++ )
		{
			if ( nIn.childNodes[i].nodeName.toUpperCase() == "TR" )
			{
				aOut.push( nIn.childNodes[i] );
			}
		}
		return aOut;
	},


	/**
	 * Equalise the heights of the rows in a given table node in a cross browser way
	 *  @returns {void}
	 *  @param   {String} nodeName Node type - thead, tbody or tfoot
	 *  @param   {Node} original Original node to take the heights from
	 *  @param   {Node} clone Copy the heights to
	 *  @private
	 */
	"_fnEqualiseHeights": function ( nodeName, original, clone )
	{
		if ( this.s.sHeightMatch == 'none' && nodeName !== 'thead' && nodeName !== 'tfoot' )
		{
			return;
		}

		var that = this,
			i, iLen, iHeight, iHeight2, iHeightOriginal, iHeightClone,
			rootOriginal = original.getElementsByTagName(nodeName)[0],
			rootClone    = clone.getElementsByTagName(nodeName)[0],
			jqBoxHack    = $('>'+nodeName+'>tr:eq(0)', original).children(':first'),
			iBoxHack     = jqBoxHack.outerHeight() - jqBoxHack.height(),
			anOriginal   = this._fnGetTrNodes( rootOriginal ),
			anClone      = this._fnGetTrNodes( rootClone ),
			heights      = [];

		for ( i=0, iLen=anClone.length ; i<iLen ; i++ )
		{
			iHeightOriginal = anOriginal[i].offsetHeight;
			iHeightClone = anClone[i].offsetHeight;
			iHeight = iHeightClone > iHeightOriginal ? iHeightClone : iHeightOriginal;

			if ( this.s.sHeightMatch == 'semiauto' )
			{
				anOriginal[i]._DTTC_iHeight = iHeight;
			}

			heights.push( iHeight );
		}

		for ( i=0, iLen=anClone.length ; i<iLen ; i++ )
		{
			anClone[i].style.height = heights[i]+"px";
			anOriginal[i].style.height = heights[i]+"px";
		}
	},

	/**
	 * Determine if the UA suffers from Firefox's overflow:scroll scrollbars
	 * not being shown bug.
	 *
	 * Firefox doesn't draw scrollbars, even if it is told to using
	 * overflow:scroll, if the div is less than 34px height. See bugs 292284 and
	 * 781885. Using UA detection here since this is particularly hard to detect
	 * using objects - its a straight up rendering error in Firefox.
	 *
	 * @return {boolean} True if Firefox error is present, false otherwise
	 */
	_firefoxScrollError: function () {
		if ( _firefoxScroll === undefined ) {
			var test = $('<div/>')
				.css( {
					position: 'absolute',
					top: 0,
					left: 0,
					height: 10,
					width: 50,
					overflow: 'scroll'
				} )
				.appendTo( 'body' );

			// Make sure this doesn't apply on Macs with 0 width scrollbars
			_firefoxScroll = (
				test[0].clientWidth === test[0].offsetWidth && this._fnDTOverflow().bar !== 0
			);

			test.remove();
		}

		return _firefoxScroll;
	}
} );



/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Statics
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/**
 * FixedColumns default settings for initialisation
 *  @name FixedColumns.defaults
 *  @namespace
 *  @static
 */
FixedColumns.defaults = /** @lends FixedColumns.defaults */{
	/**
	 * Number of left hand columns to fix in position
	 *  @type     int
	 *  @default  1
	 *  @static
	 *  @example
	 *      var  = $('#example').dataTable( {
	 *          "scrollX": "100%"
	 *      } );
	 *      new $.fn.dataTable.fixedColumns( table, {
	 *          "leftColumns": 2
	 *      } );
	 */
	"iLeftColumns": 1,

	/**
	 * Number of right hand columns to fix in position
	 *  @type     int
	 *  @default  0
	 *  @static
	 *  @example
	 *      var table = $('#example').dataTable( {
	 *          "scrollX": "100%"
	 *      } );
	 *      new $.fn.dataTable.fixedColumns( table, {
	 *          "rightColumns": 1
	 *      } );
	 */
	"iRightColumns": 0,

	/**
	 * Draw callback function which is called when FixedColumns has redrawn the fixed assets
	 *  @type     function(object, object):void
	 *  @default  null
	 *  @static
	 *  @example
	 *      var table = $('#example').dataTable( {
	 *          "scrollX": "100%"
	 *      } );
	 *      new $.fn.dataTable.fixedColumns( table, {
	 *          "drawCallback": function () {
	 *	            alert( "FixedColumns redraw" );
	 *	        }
	 *      } );
	 */
	"fnDrawCallback": null,

	/**
	 * Height matching algorthim to use. This can be "none" which will result in no height
	 * matching being applied by FixedColumns (height matching could be forced by CSS in this
	 * case), "semiauto" whereby the height calculation will be performed once, and the result
	 * cached to be used again (fnRecalculateHeight can be used to force recalculation), or
	 * "auto" when height matching is performed on every draw (slowest but must accurate)
	 *  @type     string
	 *  @default  semiauto
	 *  @static
	 *  @example
	 *      var table = $('#example').dataTable( {
	 *          "scrollX": "100%"
	 *      } );
	 *      new $.fn.dataTable.fixedColumns( table, {
	 *          "heightMatch": "auto"
	 *      } );
	 */
	"sHeightMatch": "semiauto"
};




/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Constants
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/**
 * FixedColumns version
 *  @name      FixedColumns.version
 *  @type      String
 *  @default   See code
 *  @static
 */
FixedColumns.version = "3.2.2";



/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * DataTables API integration
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

DataTable.Api.register( 'fixedColumns()', function () {
	return this;
} );

DataTable.Api.register( 'fixedColumns().update()', function () {
	return this.iterator( 'table', function ( ctx ) {
		if ( ctx._oFixedColumns ) {
			ctx._oFixedColumns.fnUpdate();
		}
	} );
} );

DataTable.Api.register( 'fixedColumns().relayout()', function () {
	return this.iterator( 'table', function ( ctx ) {
		if ( ctx._oFixedColumns ) {
			ctx._oFixedColumns.fnRedrawLayout();
		}
	} );
} );

DataTable.Api.register( 'rows().recalcHeight()', function () {
	return this.iterator( 'row', function ( ctx, idx ) {
		if ( ctx._oFixedColumns ) {
			ctx._oFixedColumns.fnRecalculateHeight( this.row(idx).node() );
		}
	} );
} );

DataTable.Api.register( 'fixedColumns().rowIndex()', function ( row ) {
	row = $(row);

	return row.parents('.DTFC_Cloned').length ?
		this.rows( { page: 'current' } ).indexes()[ row.index() ] :
		this.row( row ).index();
} );

DataTable.Api.register( 'fixedColumns().cellIndex()', function ( cell ) {
	cell = $(cell);

	if ( cell.parents('.DTFC_Cloned').length ) {
		var rowClonedIdx = cell.parent().index();
		var rowIdx = this.rows( { page: 'current' } ).indexes()[ rowClonedIdx ];
		var columnIdx;

		if ( cell.parents('.DTFC_LeftWrapper').length ) {
			columnIdx = cell.index();
		}
		else {
			var columns = this.columns().flatten().length;
			columnIdx = columns - this.context[0]._oFixedColumns.s.iRightColumns + cell.index();
		}

		return {
			row: rowIdx,
			column: this.column.index( 'toData', columnIdx ),
			columnVisible: columnIdx
		};
	}
	else {
		return this.cell( cell ).index();
	}
} );




/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Initialisation
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

// Attach a listener to the document which listens for DataTables initialisation
// events so we can automatically initialise
$(document).on( 'init.dt.fixedColumns', function (e, settings) {
	if ( e.namespace !== 'dt' ) {
		return;
	}

	var init = settings.oInit.fixedColumns;
	var defaults = DataTable.defaults.fixedColumns;

	if ( init || defaults ) {
		var opts = $.extend( {}, init, defaults );

		if ( init !== false ) {
			new FixedColumns( settings, opts );
		}
	}
} );



// Make FixedColumns accessible from the DataTables instance
$.fn.dataTable.FixedColumns = FixedColumns;
$.fn.DataTable.FixedColumns = FixedColumns;

return FixedColumns;
}));


/*! FixedHeader 3.1.2
 * ©2009-2016 SpryMedia Ltd - datatables.net/license
 */

/**
 * @summary     FixedHeader
 * @description Fix a table's header or footer, so it is always visible while
 *              scrolling
 * @version     3.1.2
 * @file        dataTables.fixedHeader.js
 * @author      SpryMedia Ltd (www.sprymedia.co.uk)
 * @contact     www.sprymedia.co.uk/contact
 * @copyright   Copyright 2009-2016 SpryMedia Ltd.
 *
 * This source file is free software, available under the following license:
 *   MIT license - http://datatables.net/license/mit
 *
 * This source file is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
 *
 * For details please refer to: http://www.datatables.net
 */

(function( factory ){
	if ( typeof define === 'function' && define.amd ) {
		// AMD
		define( ['jquery', 'datatables.net'], function ( $ ) {
			return factory( $, window, document );
		} );
	}
	else if ( typeof exports === 'object' ) {
		// CommonJS
		module.exports = function (root, $) {
			if ( ! root ) {
				root = window;
			}

			if ( ! $ || ! $.fn.dataTable ) {
				$ = require('datatables.net')(root, $).$;
			}

			return factory( $, root, root.document );
		};
	}
	else {
		// Browser
		factory( jQuery, window, document );
	}
}(function( $, window, document, undefined ) {
'use strict';
var DataTable = $.fn.dataTable;


var _instCounter = 0;

var FixedHeader = function ( dt, config ) {
	// Sanity check - you just know it will happen
	if ( ! (this instanceof FixedHeader) ) {
		throw "FixedHeader must be initialised with the 'new' keyword.";
	}

	// Allow a boolean true for defaults
	if ( config === true ) {
		config = {};
	}

	dt = new DataTable.Api( dt );

	this.c = $.extend( true, {}, FixedHeader.defaults, config );

	this.s = {
		dt: dt,
		position: {
			theadTop: 0,
			tbodyTop: 0,
			tfootTop: 0,
			tfootBottom: 0,
			width: 0,
			left: 0,
			tfootHeight: 0,
			theadHeight: 0,
			windowHeight: $(window).height(),
			visible: true
		},
		headerMode: null,
		footerMode: null,
		autoWidth: dt.settings()[0].oFeatures.bAutoWidth,
		namespace: '.dtfc'+(_instCounter++),
		scrollLeft: {
			header: -1,
			footer: -1
		},
		enable: true
	};

	this.dom = {
		floatingHeader: null,
		thead: $(dt.table().header()),
		tbody: $(dt.table().body()),
		tfoot: $(dt.table().footer()),
		header: {
			host: null,
			floating: null,
			placeholder: null
		},
		footer: {
			host: null,
			floating: null,
			placeholder: null
		}
	};

	this.dom.header.host = this.dom.thead.parent();
	this.dom.footer.host = this.dom.tfoot.parent();

	var dtSettings = dt.settings()[0];
	if ( dtSettings._fixedHeader ) {
		throw "FixedHeader already initialised on table "+dtSettings.nTable.id;
	}

	dtSettings._fixedHeader = this;

	this._constructor();
};


/*
 * Variable: FixedHeader
 * Purpose:  Prototype for FixedHeader
 * Scope:    global
 */
$.extend( FixedHeader.prototype, {
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * API methods
	 */
	
	/**
	 * Enable / disable the fixed elements
	 *
	 * @param  {boolean} enable `true` to enable, `false` to disable
	 */
	enable: function ( enable )
	{
		this.s.enable = enable;

		if ( this.c.header ) {
			this._modeChange( 'in-place', 'header', true );
		}

		if ( this.c.footer && this.dom.tfoot.length ) {
			this._modeChange( 'in-place', 'footer', true );
		}

		this.update();
	},
	
	/**
	 * Set header offset 
	 *
	 * @param  {int} new value for headerOffset
	 */
	headerOffset: function ( offset )
	{
		if ( offset !== undefined ) {
			this.c.headerOffset = offset;
			this.update();
		}

		return this.c.headerOffset;
	},
	
	/**
	 * Set footer offset
	 *
	 * @param  {int} new value for footerOffset
	 */
	footerOffset: function ( offset )
	{
		if ( offset !== undefined ) {
			this.c.footerOffset = offset;
			this.update();
		}

		return this.c.footerOffset;
	},

	
	/**
	 * Recalculate the position of the fixed elements and force them into place
	 */
	update: function ()
	{
		this._positions();
		this._scroll( true );
	},


	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Constructor
	 */
	
	/**
	 * FixedHeader constructor - adding the required event listeners and
	 * simple initialisation
	 *
	 * @private
	 */
	_constructor: function ()
	{
		var that = this;
		var dt = this.s.dt;

		$(window)
			.on( 'scroll'+this.s.namespace, function () {
				that._scroll();
			} )
			.on( 'resize'+this.s.namespace, function () {
				that.s.position.windowHeight = $(window).height();
				that.update();
			} );

		var autoHeader = $('.fh-fixedHeader');
		if ( ! this.c.headerOffset && autoHeader.length ) {
			this.c.headerOffset = autoHeader.outerHeight();
		}

		var autoFooter = $('.fh-fixedFooter');
		if ( ! this.c.footerOffset && autoFooter.length ) {
			this.c.footerOffset = autoFooter.outerHeight();
		}

		dt.on( 'column-reorder.dt.dtfc column-visibility.dt.dtfc draw.dt.dtfc column-sizing.dt.dtfc', function () {
			that.update();
		} );

		dt.on( 'destroy.dtfc', function () {
			dt.off( '.dtfc' );
			$(window).off( that.s.namespace );
		} );

		this._positions();
		this._scroll();
	},


	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Private methods
	 */

	/**
	 * Clone a fixed item to act as a place holder for the original element
	 * which is moved into a clone of the table element, and moved around the
	 * document to give the fixed effect.
	 *
	 * @param  {string}  item  'header' or 'footer'
	 * @param  {boolean} force Force the clone to happen, or allow automatic
	 *   decision (reuse existing if available)
	 * @private
	 */
	_clone: function ( item, force )
	{
		var dt = this.s.dt;
		var itemDom = this.dom[ item ];
		var itemElement = item === 'header' ?
			this.dom.thead :
			this.dom.tfoot;

		if ( ! force && itemDom.floating ) {
			// existing floating element - reuse it
			itemDom.floating.removeClass( 'fixedHeader-floating fixedHeader-locked' );
		}
		else {
			if ( itemDom.floating ) {
				itemDom.placeholder.remove();
				this._unsize( item );
				itemDom.floating.children().detach();
				itemDom.floating.remove();
			}

			itemDom.floating = $( dt.table().node().cloneNode( false ) )
				.css( 'table-layout', 'fixed' )
				.removeAttr( 'id' )
				.append( itemElement )
				.appendTo( 'body' );

			// Insert a fake thead/tfoot into the DataTable to stop it jumping around
			itemDom.placeholder = itemElement.clone( false );
			itemDom.host.prepend( itemDom.placeholder );

			// Clone widths
			this._matchWidths( itemDom.placeholder, itemDom.floating );
		}
	},

	/**
	 * Copy widths from the cells in one element to another. This is required
	 * for the footer as the footer in the main table takes its sizes from the
	 * header columns. That isn't present in the footer so to have it still
	 * align correctly, the sizes need to be copied over. It is also required
	 * for the header when auto width is not enabled
	 *
	 * @param  {jQuery} from Copy widths from
	 * @param  {jQuery} to   Copy widths to
	 * @private
	 */
	_matchWidths: function ( from, to ) {
		var get = function ( name ) {
			return $(name, from)
				.map( function () {
					return $(this).width();
				} ).toArray();
		};

		var set = function ( name, toWidths ) {
			$(name, to).each( function ( i ) {
				$(this).css( {
					width: toWidths[i],
					minWidth: toWidths[i]
				} );
			} );
		};

		var thWidths = get( 'th' );
		var tdWidths = get( 'td' );

		set( 'th', thWidths );
		set( 'td', tdWidths );
	},

	/**
	 * Remove assigned widths from the cells in an element. This is required
	 * when inserting the footer back into the main table so the size is defined
	 * by the header columns and also when auto width is disabled in the
	 * DataTable.
	 *
	 * @param  {string} item The `header` or `footer`
	 * @private
	 */
	_unsize: function ( item ) {
		var el = this.dom[ item ].floating;

		if ( el && (item === 'footer' || (item === 'header' && ! this.s.autoWidth)) ) {
			$('th, td', el).css( {
				width: '',
				minWidth: ''
			} );
		}
		else if ( el && item === 'header' ) {
			$('th, td', el).css( 'min-width', '' );
		}
	},

	/**
	 * Reposition the floating elements to take account of horizontal page
	 * scroll
	 *
	 * @param  {string} item       The `header` or `footer`
	 * @param  {int}    scrollLeft Document scrollLeft
	 * @private
	 */
	_horizontal: function ( item, scrollLeft )
	{
		var itemDom = this.dom[ item ];
		var position = this.s.position;
		var lastScrollLeft = this.s.scrollLeft;

		if ( itemDom.floating && lastScrollLeft[ item ] !== scrollLeft ) {
			itemDom.floating.css( 'left', position.left - scrollLeft );

			lastScrollLeft[ item ] = scrollLeft;
		}
	},

	/**
	 * Change from one display mode to another. Each fixed item can be in one
	 * of:
	 *
	 * * `in-place` - In the main DataTable
	 * * `in` - Floating over the DataTable
	 * * `below` - (Header only) Fixed to the bottom of the table body
	 * * `above` - (Footer only) Fixed to the top of the table body
	 * 
	 * @param  {string}  mode        Mode that the item should be shown in
	 * @param  {string}  item        'header' or 'footer'
	 * @param  {boolean} forceChange Force a redraw of the mode, even if already
	 *     in that mode.
	 * @private
	 */
	_modeChange: function ( mode, item, forceChange )
	{
		var dt = this.s.dt;
		var itemDom = this.dom[ item ];
		var position = this.s.position;

		// Record focus. Browser's will cause input elements to loose focus if
		// they are inserted else where in the doc
		var tablePart = this.dom[ item==='footer' ? 'tfoot' : 'thead' ];
		var focus = $.contains( tablePart[0], document.activeElement ) ?
			document.activeElement :
			null;

		if ( mode === 'in-place' ) {
			// Insert the header back into the table's real header
			if ( itemDom.placeholder ) {
				itemDom.placeholder.remove();
				itemDom.placeholder = null;
			}

			this._unsize( item );

			if ( item === 'header' ) {
				itemDom.host.prepend( this.dom.thead );
			}
			else {
				itemDom.host.append( this.dom.tfoot );
			}

			if ( itemDom.floating ) {
				itemDom.floating.remove();
				itemDom.floating = null;
			}
		}
		else if ( mode === 'in' ) {
			// Remove the header from the read header and insert into a fixed
			// positioned floating table clone
			this._clone( item, forceChange );

			itemDom.floating
				.addClass( 'fixedHeader-floating' )
				.css( item === 'header' ? 'top' : 'bottom', this.c[item+'Offset'] )
				.css( 'left', position.left+'px' )
				.css( 'width', position.width+'px' );

			if ( item === 'footer' ) {
				itemDom.floating.css( 'top', '' );
			}
		}
		else if ( mode === 'below' ) { // only used for the header
			// Fix the position of the floating header at base of the table body
			this._clone( item, forceChange );

			itemDom.floating
				.addClass( 'fixedHeader-locked' )
				.css( 'top', position.tfootTop - position.theadHeight )
				.css( 'left', position.left+'px' )
				.css( 'width', position.width+'px' );
		}
		else if ( mode === 'above' ) { // only used for the footer
			// Fix the position of the floating footer at top of the table body
			this._clone( item, forceChange );

			itemDom.floating
				.addClass( 'fixedHeader-locked' )
				.css( 'top', position.tbodyTop )
				.css( 'left', position.left+'px' )
				.css( 'width', position.width+'px' );
		}

		// Restore focus if it was lost
		if ( focus && focus !== document.activeElement ) {
			focus.focus();
		}

		this.s.scrollLeft.header = -1;
		this.s.scrollLeft.footer = -1;
		this.s[item+'Mode'] = mode;
	},

	/**
	 * Cache the positional information that is required for the mode
	 * calculations that FixedHeader performs.
	 *
	 * @private
	 */
	_positions: function ()
	{
		var dt = this.s.dt;
		var table = dt.table();
		var position = this.s.position;
		var dom = this.dom;
		var tableNode = $(table.node());

		// Need to use the header and footer that are in the main table,
		// regardless of if they are clones, since they hold the positions we
		// want to measure from
		var thead = tableNode.children('thead');
		var tfoot = tableNode.children('tfoot');
		var tbody = dom.tbody;

		position.visible = tableNode.is(':visible');
		position.width = tableNode.outerWidth();
		position.left = tableNode.offset().left;
		position.theadTop = thead.offset().top;
		position.tbodyTop = tbody.offset().top;
		position.theadHeight = position.tbodyTop - position.theadTop;

		if ( tfoot.length ) {
			position.tfootTop = tfoot.offset().top;
			position.tfootBottom = position.tfootTop + tfoot.outerHeight();
			position.tfootHeight = position.tfootBottom - position.tfootTop;
		}
		else {
			position.tfootTop = position.tbodyTop + tbody.outerHeight();
			position.tfootBottom = position.tfootTop;
			position.tfootHeight = position.tfootTop;
		}
	},


	/**
	 * Mode calculation - determine what mode the fixed items should be placed
	 * into.
	 *
	 * @param  {boolean} forceChange Force a redraw of the mode, even if already
	 *     in that mode.
	 * @private
	 */
	_scroll: function ( forceChange )
	{
		var windowTop = $(document).scrollTop();
		var windowLeft = $(document).scrollLeft();
		var position = this.s.position;
		var headerMode, footerMode;

		if ( ! this.s.enable ) {
			return;
		}

		if ( this.c.header ) {
			if ( ! position.visible || windowTop <= position.theadTop - this.c.headerOffset ) {
				headerMode = 'in-place';
			}
			else if ( windowTop <= position.tfootTop - position.theadHeight - this.c.headerOffset ) {
				headerMode = 'in';
			}
			else {
				headerMode = 'below';
			}

			if ( forceChange || headerMode !== this.s.headerMode ) {
				this._modeChange( headerMode, 'header', forceChange );
			}

			this._horizontal( 'header', windowLeft );
		}

		if ( this.c.footer && this.dom.tfoot.length ) {
			if ( ! position.visible || windowTop + position.windowHeight >= position.tfootBottom + this.c.footerOffset ) {
				footerMode = 'in-place';
			}
			else if ( position.windowHeight + windowTop > position.tbodyTop + position.tfootHeight + this.c.footerOffset ) {
				footerMode = 'in';
			}
			else {
				footerMode = 'above';
			}

			if ( forceChange || footerMode !== this.s.footerMode ) {
				this._modeChange( footerMode, 'footer', forceChange );
			}

			this._horizontal( 'footer', windowLeft );
		}
	}
} );


/**
 * Version
 * @type {String}
 * @static
 */
FixedHeader.version = "3.1.2";

/**
 * Defaults
 * @type {Object}
 * @static
 */
FixedHeader.defaults = {
	header: true,
	footer: false,
	headerOffset: 0,
	footerOffset: 0
};


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * DataTables interfaces
 */

// Attach for constructor access
$.fn.dataTable.FixedHeader = FixedHeader;
$.fn.DataTable.FixedHeader = FixedHeader;


// DataTables creation - check if the FixedHeader option has been defined on the
// table and if so, initialise
$(document).on( 'init.dt.dtfh', function (e, settings, json) {
	if ( e.namespace !== 'dt' ) {
		return;
	}

	var init = settings.oInit.fixedHeader;
	var defaults = DataTable.defaults.fixedHeader;

	if ( (init || defaults) && ! settings._fixedHeader ) {
		var opts = $.extend( {}, defaults, init );

		if ( init !== false ) {
			new FixedHeader( settings, opts );
		}
	}
} );

// DataTables API methods
DataTable.Api.register( 'fixedHeader()', function () {} );

DataTable.Api.register( 'fixedHeader.adjust()', function () {
	return this.iterator( 'table', function ( ctx ) {
		var fh = ctx._fixedHeader;

		if ( fh ) {
			fh.update();
		}
	} );
} );

DataTable.Api.register( 'fixedHeader.enable()', function ( flag ) {
	return this.iterator( 'table', function ( ctx ) {
		var fh = ctx._fixedHeader;

		if ( fh ) {
			fh.enable( flag !== undefined ? flag : true );
		}
	} );
} );

DataTable.Api.register( 'fixedHeader.disable()', function ( ) {
	return this.iterator( 'table', function ( ctx ) {
		var fh = ctx._fixedHeader;

		if ( fh ) {
			fh.enable( false );
		}
	} );
} );

$.each( ['header', 'footer'], function ( i, el ) {
	DataTable.Api.register( 'fixedHeader.'+el+'Offset()', function ( offset ) {
		var ctx = this.context;

		if ( offset === undefined ) {
			return ctx.length && ctx[0]._fixedHeader ?
				ctx[0]._fixedHeader[el +'Offset']() :
				undefined;
		}

		return this.iterator( 'table', function ( ctx ) {
			var fh = ctx._fixedHeader;

			if ( fh ) {
				fh[ el +'Offset' ]( offset );
			}
		} );
	} );
} );


return FixedHeader;
}));


/*! KeyTable 2.1.2
 * ©2009-2016 SpryMedia Ltd - datatables.net/license
 */

/**
 * @summary     KeyTable
 * @description Spreadsheet like keyboard navigation for DataTables
 * @version     2.1.2
 * @file        dataTables.keyTable.js
 * @author      SpryMedia Ltd (www.sprymedia.co.uk)
 * @contact     www.sprymedia.co.uk/contact
 * @copyright   Copyright 2009-2016 SpryMedia Ltd.
 *
 * This source file is free software, available under the following license:
 *   MIT license - http://datatables.net/license/mit
 *
 * This source file is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
 *
 * For details please refer to: http://www.datatables.net
 */

(function( factory ){
	if ( typeof define === 'function' && define.amd ) {
		// AMD
		define( ['jquery', 'datatables.net'], function ( $ ) {
			return factory( $, window, document );
		} );
	}
	else if ( typeof exports === 'object' ) {
		// CommonJS
		module.exports = function (root, $) {
			if ( ! root ) {
				root = window;
			}

			if ( ! $ || ! $.fn.dataTable ) {
				$ = require('datatables.net')(root, $).$;
			}

			return factory( $, root, root.document );
		};
	}
	else {
		// Browser
		factory( jQuery, window, document );
	}
}(function( $, window, document, undefined ) {
'use strict';
var DataTable = $.fn.dataTable;


var KeyTable = function ( dt, opts ) {
	// Sanity check that we are using DataTables 1.10 or newer
	if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.8' ) ) {
		throw 'KeyTable requires DataTables 1.10.8 or newer';
	}

	// User and defaults configuration object
	this.c = $.extend( true, {},
		DataTable.defaults.keyTable,
		KeyTable.defaults,
		opts
	);

	// Internal settings
	this.s = {
		/** @type {DataTable.Api} DataTables' API instance */
		dt: new DataTable.Api( dt ),

		enable: true,

		/** @type {bool} Flag for if a draw is triggered by focus */
		focusDraw: false
	};

	// DOM items
	this.dom = {

	};

	// Check if row reorder has already been initialised on this table
	var settings = this.s.dt.settings()[0];
	var exisiting = settings.keytable;
	if ( exisiting ) {
		return exisiting;
	}

	settings.keytable = this;
	this._constructor();
};


$.extend( KeyTable.prototype, {
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * API methods for DataTables API interface
	 */
	
	/**
	 * Blur the table's cell focus
	 */
	blur: function ()
	{
		this._blur();
	},

	/**
	 * Enable cell focus for the table
	 *
	 * @param  {string} state Can be `true`, `false` or `-string navigation-only`
	 */
	enable: function ( state )
	{
		this.s.enable = state;
	},

	/**
	 * Focus on a cell
	 * @param  {integer} row    Row index
	 * @param  {integer} column Column index
	 */
	focus: function ( row, column )
	{
		this._focus( this.s.dt.cell( row, column ) );
	},

	/**
	 * Is the cell focused
	 * @param  {object} cell Cell index to check
	 * @returns {boolean} true if focused, false otherwise
	 */
	focused: function ( cell )
	{
		var lastFocus = this.s.lastFocus;

		if ( ! lastFocus ) {
			return false;
		}

		var lastIdx = this.s.lastFocus.index();
		return cell.row === lastIdx.row && cell.column === lastIdx.column;
	},


	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Constructor
	 */

	/**
	 * Initialise the KeyTable instance
	 *
	 * @private
	 */
	_constructor: function ()
	{
		this._tabInput();

		var that = this;
		var dt = this.s.dt;
		var table = $( dt.table().node() );

		// Need to be able to calculate the cell positions relative to the table
		if ( table.css('position') === 'static' ) {
			table.css( 'position', 'relative' );
		}

		// Click to focus
		$( dt.table().body() ).on( 'click.keyTable', 'th, td', function () {
			if ( that.s.enable === false ) {
				return;
			}

			var cell = dt.cell( this );

			if ( ! cell.any() ) {
				return;
			}

			that._focus( cell, null, false );
		} );

		// Key events
		$( document ).on( 'keydown.keyTable', function (e) {
			that._key( e );
		} );

		// Click blur
		if ( this.c.blurable ) {
			$( document ).on( 'click.keyTable', function ( e ) {
				// Click on the search input will blur focus
				if ( $(e.target).parents( '.dataTables_filter' ).length ) {
					that._blur();
				}

				// If the click was inside the DataTables container, don't blur
				if ( $(e.target).parents().filter( dt.table().container() ).length ) {
					return;
				}

				// Don't blur in Editor form
				if ( $(e.target).parents('div.DTE').length ) {
					return;
				}

				that._blur();
			} );
		}

		if ( this.c.editor ) {
			dt.on( 'key.keyTable', function ( e, dt, key, cell, orig ) {
				that._editor( key, orig );
			} );
		}

		// Stave saving
		if ( dt.settings()[0].oFeatures.bStateSave ) {
			dt.on( 'stateSaveParams.keyTable', function (e, s, d) {
				d.keyTable = that.s.lastFocus ?
					that.s.lastFocus.index() :
					null;
			} );
		}

		// Reload - re-focus on the currently selected item. In SSP mode this
		// has the effect of keeping the focus in position when changing page as
		// well (which is different from how client-side processing works).
		dt.on( 'xhr.keyTable', function ( e ) {
			if ( that.s.focusDraw ) {
				// Triggered by server-side processing, and thus `_focus` will
				// do the refocus on the next draw event
				return;
			}

			var lastFocus = that.s.lastFocus;

			if ( lastFocus ) {
				that.s.lastFocus = null;

				dt.one( 'draw', function () {
					that._focus( lastFocus );
				} );
			}
		} );

		dt.on( 'destroy.keyTable', function () {
			dt.off( '.keyTable' );
			$( dt.table().body() ).off( 'click.keyTable', 'th, td' );
			$( document.body )
				.off( 'keydown.keyTable' )
				.off( 'click.keyTable' );
		} );

		// Initial focus comes from state or options
		var state = dt.state.loaded();

		if ( state && state.keyTable ) {
			// Wait until init is done
			dt.one( 'init', function () {
				var cell = dt.cell( state.keyTable );

				// Ensure that the saved cell still exists
				if ( cell.any() ) {
					cell.focus();
				}
			} );
		}
		else if ( this.c.focus ) {
			dt.cell( this.c.focus ).focus();
		}
	},




	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Private methods
	 */

	/**
	 * Blur the control
	 *
	 * @private
	 */
	_blur: function ()
	{
		if ( ! this.s.enable || ! this.s.lastFocus ) {
			return;
		}

		var cell = this.s.lastFocus;

		$( cell.node() ).removeClass( this.c.className );
		this.s.lastFocus = null;

		this._emitEvent( 'key-blur', [ this.s.dt, cell ] );
	},


	/**
	 * Get an array of the column indexes that KeyTable can operate on. This
	 * is a merge of the user supplied columns and the visible columns.
	 *
	 * @private
	 */
	_columns: function ()
	{
		var dt = this.s.dt;
		var user = dt.columns( this.c.columns ).indexes();
		var out = [];

		dt.columns( ':visible' ).every( function (i) {
			if ( user.indexOf( i ) !== -1 ) {
				out.push( i );
			}
		} );

		return out;
	},


	/**
	 * Perform excel like navigation for Editor by triggering an edit on key
	 * press
	 *
	 * @param  {integer} key Key code for the pressed key
	 * @param  {object} orig Original event
	 * @private
	 */
	_editor: function ( key, orig )
	{
		var dt = this.s.dt;
		var editor = this.c.editor;

		orig.stopPropagation();

		// Return key should do nothing - for textareas's it would empty the
		// contents
		if ( key === 13 ) {
			orig.preventDefault();
		}

		editor.inline( this.s.lastFocus.index() );

		// Excel style - select all text
		var input = $('div.DTE input, div.DTE textarea');
		if ( input.length ) {
			input[0].select();
		}

		// Reduce the keys the Keys listens for
		dt.keys.enable( 'navigation-only' );

		// On blur of the navigation submit
		dt.one( 'key-blur.editor', function () {
			if ( editor.displayed() ) {
				editor.submit();
			}
		} );

		// Restore full key navigation on close
		editor.one( 'close', function () {
			dt.keys.enable( true );
			dt.off( 'key-blur.editor' );
		} );
	},


	/**
	 * Emit an event on the DataTable for listeners
	 *
	 * @param  {string} name Event name
	 * @param  {array} args Event arguments
	 * @private
	 */
	_emitEvent: function ( name, args )
	{
		this.s.dt.iterator( 'table', function ( ctx, i ) {
			$(ctx.nTable).triggerHandler( name, args );
		} );
	},


	/**
	 * Focus on a particular cell, shifting the table's paging if required
	 *
	 * @param  {DataTables.Api|integer} row Can be given as an API instance that
	 *   contains the cell to focus or as an integer. As the latter it is the
	 *   visible row index - NOT the data index
	 * @param  {integer} [column] Not required if a cell is given as the first
	 *   parameter. Otherwise this is the column data index for the cell to
	 *   focus on
	 * @param {boolean} [shift=true] Should the viewport be moved to show cell
	 * @private
	 */
	_focus: function ( row, column, shift )
	{
		var that = this;
		var dt = this.s.dt;
		var pageInfo = dt.page.info();
		var lastFocus = this.s.lastFocus;

		if ( ! this.s.enable ) {
			return;
		}

		if ( typeof row !== 'number' ) {
			// Convert the cell to a row and column
			var index = row.index();
			column = index.column;
			row = dt
				.rows( { filter: 'applied', order: 'applied' } )
				.indexes()
				.indexOf( index.row );

			// For server-side processing normalise the row by adding the start
			// point, since `rows().indexes()` includes only rows that are
			// available at the client-side
			if ( pageInfo.serverSide ) {
				row += pageInfo.start;
			}
		}

		// Is the row on the current page? If not, we need to redraw to show the
		// page
		if ( pageInfo.length !== -1 && (row < pageInfo.start || row >= pageInfo.start+pageInfo.length) ) {
			this.s.focusDraw = true;

			dt
				.one( 'draw', function () {
					that.s.focusDraw = false;
					that._focus( row, column );
				} )
				.page( Math.floor( row / pageInfo.length ) )
				.draw( false );

			return;
		}

		// In the available columns?
		if ( $.inArray( column, this._columns() ) === -1 ) {
			return;
		}

		// De-normalise the server-side processing row, so we select the row
		// in its displayed position
		if ( pageInfo.serverSide ) {
			row -= pageInfo.start;
		}

		var cell = dt.cell( ':eq('+row+')', column, {search: 'applied'} );

		if ( lastFocus ) {
			// Don't trigger a refocus on the same cell
			if ( lastFocus.node() === cell.node() ) {
				return;
			}

			// Otherwise blur the old focus
			this._blur();
		}

		var node = $( cell.node() );
		node.addClass( this.c.className );

		// Shift viewpoint and page to make cell visible
		if ( shift === undefined || shift === true ) {
			this._scroll( $(window), $(document.body), node, 'offset' );

			var bodyParent = dt.table().body().parentNode;
			if ( bodyParent !== dt.table().header().parentNode ) {
				var parent = $(bodyParent.parentNode);

				this._scroll( parent, parent, node, 'position' );
			}
		}

		// Event and finish
		this.s.lastFocus = cell;

		this._emitEvent( 'key-focus', [ this.s.dt, cell ] );
		dt.state.save();
	},


	/**
	 * Handle key press
	 *
	 * @param  {object} e Event
	 * @private
	 */
	_key: function ( e )
	{
		if ( ! this.s.enable ) {
			return;
		}

		if ( e.keyCode === 0 || e.ctrlKey || e.metaKey || e.altKey ) {
			return;
		}

		// If not focused, then there is no key action to take
		var cell = this.s.lastFocus;
		if ( ! cell ) {
			return;
		}

		var that = this;
		var dt = this.s.dt;

		// If we are not listening for this key, do nothing
		if ( this.c.keys && $.inArray( e.keyCode, this.c.keys ) === -1 ) {
			return;
		}

		switch( e.keyCode ) {
			case 9: // tab
				this._shift( e, e.shiftKey ? 'left' : 'right', true );
				break;

			case 27: // esc
				if ( this.s.blurable && this.s.enable === true ) {
					this._blur();
				}
				break;

			case 33: // page up (previous page)
			case 34: // page down (next page)
				e.preventDefault();
				var index = dt.cells( {page: 'current'} ).nodes().indexOf( cell.node() );

				dt
					.one( 'draw', function () {
						var nodes = dt.cells( {page: 'current'} ).nodes();

						that._focus( dt.cell( index < nodes.length ?
							nodes[ index ] :
							nodes[ nodes.length-1 ]
						) );
					} )
					.page( e.keyCode === 33 ? 'previous' : 'next' )
					.draw( false );
				break;

			case 35: // end (end of current page)
			case 36: // home (start of current page)
				e.preventDefault();
				var indexes = dt.cells( {page: 'current'} ).indexes();

				this._focus( dt.cell(
					indexes[ e.keyCode === 35 ? indexes.length-1 : 0 ]
				) );
				break;

			case 37: // left arrow
				this._shift( e, 'left' );
				break;

			case 38: // up arrow
				this._shift( e, 'up' );
				break;

			case 39: // right arrow
				this._shift( e, 'right' );
				break;

			case 40: // down arrow
				this._shift( e, 'down' );
				break;

			default:
				// Everything else - pass through only when fully enabled
				if ( this.s.enable === true ) {
					this._emitEvent( 'key', [ dt, e.keyCode, this.s.lastFocus, e ] );
				}
				break;
		}
	},


	/**
	 * Scroll a container to make a cell visible in it. This can be used for
	 * both DataTables scrolling and native window scrolling.
	 *
	 * @param  {jQuery} container Scrolling container
	 * @param  {jQuery} scroller  Item being scrolled
	 * @param  {jQuery} cell      Cell in the scroller
	 * @param  {string} posOff    `position` or `offset` - which to use for the
	 *   calculation. `offset` for the document, otherwise `position`
	 * @private
	 */
	_scroll: function ( container, scroller, cell, posOff )
	{
		var offset = cell[posOff]();
		var height = cell.outerHeight();
		var width = cell.outerWidth();

		var scrollTop = scroller.scrollTop();
		var scrollLeft = scroller.scrollLeft();
		var containerHeight = container.height();
		var containerWidth = container.width();

		// Top correction
		if ( offset.top < scrollTop ) {
			scroller.scrollTop( offset.top );
		}

		// Left correction
		if ( offset.left < scrollLeft ) {
			scroller.scrollLeft( offset.left );
		}

		// Bottom correction
		if ( offset.top + height > scrollTop + containerHeight && height < containerHeight ) {
			scroller.scrollTop( offset.top + height - containerHeight );
		}

		// Right correction
		if ( offset.left + width > scrollLeft + containerWidth && width < containerWidth ) {
			scroller.scrollLeft( offset.left + width - containerWidth );
		}
	},


	/**
	 * Calculate a single offset movement in the table - up, down, left and
	 * right and then perform the focus if possible
	 *
	 * @param  {object}  e           Event object
	 * @param  {string}  direction   Movement direction
	 * @param  {boolean} keyBlurable `true` if the key press can result in the
	 *   table being blurred. This is so arrow keys won't blur the table, but
	 *   tab will.
	 * @private
	 */
	_shift: function ( e, direction, keyBlurable )
	{
		var that         = this;
		var dt           = this.s.dt;
		var pageInfo     = dt.page.info();
		var rows         = pageInfo.recordsDisplay;
		var currentCell  = this.s.lastFocus;
		var columns      = this._columns();

		if ( ! currentCell ) {
			return;
		}

		var currRow = dt
			.rows( { filter: 'applied', order: 'applied' } )
			.indexes()
			.indexOf( currentCell.index().row );

		// When server-side processing, `rows().indexes()` only gives the rows
		// that are available at the client-side, so we need to normalise the
		// row's current position by the display start point
		if ( pageInfo.serverSide ) {
			currRow += pageInfo.start;
		}

		var currCol = dt
			.columns( columns )
			.indexes()
			.indexOf( currentCell.index().column );

		var
			row = currRow,
			column = columns[ currCol ]; // row is the display, column is an index

		if ( direction === 'right' ) {
			if ( currCol >= columns.length - 1 ) {
				row++;
				column = columns[0];
			}
			else {
				column = columns[ currCol+1 ];
			}
		}
		else if ( direction === 'left' ) {
			if ( currCol === 0 ) {
				row--;
				column = columns[ columns.length - 1 ];
			}
			else {
				column = columns[ currCol-1 ];
			}
		}
		else if ( direction === 'up' ) {
			row--;
		}
		else if ( direction === 'down' ) {
			row++;
		}

		if ( row >= 0 && row < rows && $.inArray( column, columns ) !== -1
		) {
			e.preventDefault();

			this._focus( row, column );
		}
		else if ( ! keyBlurable || ! this.c.blurable ) {
			// No new focus, but if the table isn't blurable, then don't loose
			// focus
			e.preventDefault();
		}
		else {
			this._blur();
		}
	},


	/**
	 * Create a hidden input element that can receive focus on behalf of the
	 * table
	 *
	 * @private
	 */
	_tabInput: function ()
	{
		var that = this;
		var dt = this.s.dt;
		var tabIndex = this.c.tabIndex !== null ?
			this.c.tabIndex :
			dt.settings()[0].iTabIndex;

		if ( tabIndex == -1 ) {
			return;
		}

		var div = $('<div><input type="text" tabindex="'+tabIndex+'"/></div>')
			.css( {
				position: 'absolute',
				height: 1,
				width: 0,
				overflow: 'hidden'
			} )
			.insertBefore( dt.table().node() );

		div.children().on( 'focus', function () {
			that._focus( dt.cell(':eq(0)', '0:visible', {page: 'current'}) );
		} );
	}
} );


/**
 * KeyTable default settings for initialisation
 *
 * @namespace
 * @name KeyTable.defaults
 * @static
 */
KeyTable.defaults = {
	/**
	 * Can focus be removed from the table
	 * @type {Boolean}
	 */
	blurable: true,

	/**
	 * Class to give to the focused cell
	 * @type {String}
	 */
	className: 'focus',

	/**
	 * Columns that can be focused. This is automatically merged with the
	 * visible columns as only visible columns can gain focus.
	 * @type {String}
	 */
	columns: '', // all

	/**
	 * Editor instance to automatically perform Excel like navigation
	 * @type {Editor}
	 */
	editor: null,

	/**
	 * Select a cell to automatically select on start up. `null` for no
	 * automatic selection
	 * @type {cell-selector}
	 */
	focus: null,

	/**
	 * Array of keys to listen for
	 * @type {null|array}
	 */
	keys: null,

	/**
	 * Tab index for where the table should sit in the document's tab flow
	 * @type {integer|null}
	 */
	tabIndex: null
};



KeyTable.version = "2.1.2";


$.fn.dataTable.KeyTable = KeyTable;
$.fn.DataTable.KeyTable = KeyTable;


DataTable.Api.register( 'cell.blur()', function () {
	return this.iterator( 'table', function (ctx) {
		if ( ctx.keytable ) {
			ctx.keytable.blur();
		}
	} );
} );

DataTable.Api.register( 'cell().focus()', function () {
	return this.iterator( 'cell', function (ctx, row, column) {
		if ( ctx.keytable ) {
			ctx.keytable.focus( row, column );
		}
	} );
} );

DataTable.Api.register( 'keys.disable()', function () {
	return this.iterator( 'table', function (ctx) {
		if ( ctx.keytable ) {
			ctx.keytable.enable( false );
		}
	} );
} );

DataTable.Api.register( 'keys.enable()', function ( opts ) {
	return this.iterator( 'table', function (ctx) {
		if ( ctx.keytable ) {
			ctx.keytable.enable( opts === undefined ? true : opts );
		}
	} );
} );

// Cell selector
DataTable.ext.selector.cell.push( function ( settings, opts, cells ) {
	var focused = opts.focused;
	var kt = settings.keytable;
	var out = [];

	if ( ! kt || focused === undefined ) {
		return cells;
	}

	for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
		if ( (focused === true &&  kt.focused( cells[i] ) ) ||
			 (focused === false && ! kt.focused( cells[i] ) )
		) {
			out.push( cells[i] );
		}
	}

	return out;
} );


// Attach a listener to the document which listens for DataTables initialisation
// events so we can automatically initialise
$(document).on( 'preInit.dt.dtk', function (e, settings, json) {
	if ( e.namespace !== 'dt' ) {
		return;
	}

	var init = settings.oInit.keys;
	var defaults = DataTable.defaults.keys;

	if ( init || defaults ) {
		var opts = $.extend( {}, init, defaults );

		if ( init !== false ) {
			new KeyTable( settings, opts  );
		}
	}
} );


return KeyTable;
}));


/*! Responsive 2.1.0
 * 2014-2016 SpryMedia Ltd - datatables.net/license
 */

/**
 * @summary     Responsive
 * @description Responsive tables plug-in for DataTables
 * @version     2.1.0
 * @file        dataTables.responsive.js
 * @author      SpryMedia Ltd (www.sprymedia.co.uk)
 * @contact     www.sprymedia.co.uk/contact
 * @copyright   Copyright 2014-2016 SpryMedia Ltd.
 *
 * This source file is free software, available under the following license:
 *   MIT license - http://datatables.net/license/mit
 *
 * This source file is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
 *
 * For details please refer to: http://www.datatables.net
 */
(function( factory ){
	if ( typeof define === 'function' && define.amd ) {
		// AMD
		define( ['jquery', 'datatables.net'], function ( $ ) {
			return factory( $, window, document );
		} );
	}
	else if ( typeof exports === 'object' ) {
		// CommonJS
		module.exports = function (root, $) {
			if ( ! root ) {
				root = window;
			}

			if ( ! $ || ! $.fn.dataTable ) {
				$ = require('datatables.net')(root, $).$;
			}

			return factory( $, root, root.document );
		};
	}
	else {
		// Browser
		factory( jQuery, window, document );
	}
}(function( $, window, document, undefined ) {
'use strict';
var DataTable = $.fn.dataTable;


/**
 * Responsive is a plug-in for the DataTables library that makes use of
 * DataTables' ability to change the visibility of columns, changing the
 * visibility of columns so the displayed columns fit into the table container.
 * The end result is that complex tables will be dynamically adjusted to fit
 * into the viewport, be it on a desktop, tablet or mobile browser.
 *
 * Responsive for DataTables has two modes of operation, which can used
 * individually or combined:
 *
 * * Class name based control - columns assigned class names that match the
 *   breakpoint logic can be shown / hidden as required for each breakpoint.
 * * Automatic control - columns are automatically hidden when there is no
 *   room left to display them. Columns removed from the right.
 *
 * In additional to column visibility control, Responsive also has built into
 * options to use DataTables' child row display to show / hide the information
 * from the table that has been hidden. There are also two modes of operation
 * for this child row display:
 *
 * * Inline - when the control element that the user can use to show / hide
 *   child rows is displayed inside the first column of the table.
 * * Column - where a whole column is dedicated to be the show / hide control.
 *
 * Initialisation of Responsive is performed by:
 *
 * * Adding the class `responsive` or `dt-responsive` to the table. In this case
 *   Responsive will automatically be initialised with the default configuration
 *   options when the DataTable is created.
 * * Using the `responsive` option in the DataTables configuration options. This
 *   can also be used to specify the configuration options, or simply set to
 *   `true` to use the defaults.
 *
 *  @class
 *  @param {object} settings DataTables settings object for the host table
 *  @param {object} [opts] Configuration options
 *  @requires jQuery 1.7+
 *  @requires DataTables 1.10.3+
 *
 *  @example
 *      $('#example').DataTable( {
 *        responsive: true
 *      } );
 *    } );
 */
var Responsive = function ( settings, opts ) {
	// Sanity check that we are using DataTables 1.10 or newer
	if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.3' ) ) {
		throw 'DataTables Responsive requires DataTables 1.10.3 or newer';
	}

	this.s = {
		dt: new DataTable.Api( settings ),
		columns: [],
		current: []
	};

	// Check if responsive has already been initialised on this table
	if ( this.s.dt.settings()[0].responsive ) {
		return;
	}

	// details is an object, but for simplicity the user can give it as a string
	// or a boolean
	if ( opts && typeof opts.details === 'string' ) {
		opts.details = { type: opts.details };
	}
	else if ( opts && opts.details === false ) {
		opts.details = { type: false };
	}
	else if ( opts && opts.details === true ) {
		opts.details = { type: 'inline' };
	}

	this.c = $.extend( true, {}, Responsive.defaults, DataTable.defaults.responsive, opts );
	settings.responsive = this;
	this._constructor();
};

$.extend( Responsive.prototype, {
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Constructor
	 */

	/**
	 * Initialise the Responsive instance
	 *
	 * @private
	 */
	_constructor: function ()
	{
		var that = this;
		var dt = this.s.dt;
		var dtPrivateSettings = dt.settings()[0];
		var oldWindowWidth = $(window).width();

		dt.settings()[0]._responsive = this;

		// Use DataTables' throttle function to avoid processor thrashing on
		// resize
		$(window).on( 'resize.dtr orientationchange.dtr', DataTable.util.throttle( function () {
			// iOS has a bug whereby resize can fire when only scrolling
			// See: http://stackoverflow.com/questions/8898412
			var width = $(window).width();

			if ( width !== oldWindowWidth ) {
				that._resize();
				oldWindowWidth = width;
			}
		} ) );

		// DataTables doesn't currently trigger an event when a row is added, so
		// we need to hook into its private API to enforce the hidden rows when
		// new data is added
		dtPrivateSettings.oApi._fnCallbackReg( dtPrivateSettings, 'aoRowCreatedCallback', function (tr, data, idx) {
			if ( $.inArray( false, that.s.current ) !== -1 ) {
				$('td, th', tr).each( function ( i ) {
					var idx = dt.column.index( 'toData', i );

					if ( that.s.current[idx] === false ) {
						$(this).css('display', 'none');
					}
				} );
			}
		} );

		// Destroy event handler
		dt.on( 'destroy.dtr', function () {
			dt.off( '.dtr' );
			$( dt.table().body() ).off( '.dtr' );
			$(window).off( 'resize.dtr orientationchange.dtr' );

			// Restore the columns that we've hidden
			$.each( that.s.current, function ( i, val ) {
				if ( val === false ) {
					that._setColumnVis( i, true );
				}
			} );
		} );

		// Reorder the breakpoints array here in case they have been added out
		// of order
		this.c.breakpoints.sort( function (a, b) {
			return a.width < b.width ? 1 :
				a.width > b.width ? -1 : 0;
		} );

		this._classLogic();
		this._resizeAuto();

		// Details handler
		var details = this.c.details;

		if ( details.type !== false ) {
			that._detailsInit();

			// DataTables will trigger this event on every column it shows and
			// hides individually
			dt.on( 'column-visibility.dtr', function (e, ctx, col, vis) {
				that._classLogic();
				that._resizeAuto();
				that._resize();
			} );

			// Redraw the details box on each draw which will happen if the data
			// has changed. This is used until DataTables implements a native
			// `updated` event for rows
			dt.on( 'draw.dtr', function () {
				that._redrawChildren();
			} );

			$(dt.table().node()).addClass( 'dtr-'+details.type );
		}

		dt.on( 'column-reorder.dtr', function (e, settings, details) {
			that._classLogic();
			that._resizeAuto();
			that._resize();
		} );

		// Change in column sizes means we need to calc
		dt.on( 'column-sizing.dtr', function () {
			that._resizeAuto();
			that._resize();
		});

		dt.on( 'init.dtr', function (e, settings, details) {
			that._resizeAuto();
			that._resize();

			// If columns were hidden, then DataTables needs to adjust the
			// column sizing
			if ( $.inArray( false, that.s.current ) ) {
				dt.columns.adjust();
			}
		} );

		// First pass - draw the table for the current viewport size
		this._resize();
	},


	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Private methods
	 */

	/**
	 * Calculate the visibility for the columns in a table for a given
	 * breakpoint. The result is pre-determined based on the class logic if
	 * class names are used to control all columns, but the width of the table
	 * is also used if there are columns which are to be automatically shown
	 * and hidden.
	 *
	 * @param  {string} breakpoint Breakpoint name to use for the calculation
	 * @return {array} Array of boolean values initiating the visibility of each
	 *   column.
	 *  @private
	 */
	_columnsVisiblity: function ( breakpoint )
	{
		var dt = this.s.dt;
		var columns = this.s.columns;
		var i, ien;

		// Create an array that defines the column ordering based first on the
		// column's priority, and secondly the column index. This allows the
		// columns to be removed from the right if the priority matches
		var order = columns
			.map( function ( col, idx ) {
				return {
					columnIdx: idx,
					priority: col.priority
				};
			} )
			.sort( function ( a, b ) {
				if ( a.priority !== b.priority ) {
					return a.priority - b.priority;
				}
				return a.columnIdx - b.columnIdx;
			} );

		// Class logic - determine which columns are in this breakpoint based
		// on the classes. If no class control (i.e. `auto`) then `-` is used
		// to indicate this to the rest of the function
		var display = $.map( columns, function ( col ) {
			return col.auto && col.minWidth === null ?
				false :
				col.auto === true ?
					'-' :
					$.inArray( breakpoint, col.includeIn ) !== -1;
		} );

		// Auto column control - first pass: how much width is taken by the
		// ones that must be included from the non-auto columns
		var requiredWidth = 0;
		for ( i=0, ien=display.length ; i<ien ; i++ ) {
			if ( display[i] === true ) {
				requiredWidth += columns[i].minWidth;
			}
		}

		// Second pass, use up any remaining width for other columns. For
		// scrolling tables we need to subtract the width of the scrollbar. It
		// may not be requires which makes this sub-optimal, but it would
		// require another full redraw to make complete use of those extra few
		// pixels
		var scrolling = dt.settings()[0].oScroll;
		var bar = scrolling.sY || scrolling.sX ? scrolling.iBarWidth : 0;
		var widthAvailable = dt.table().container().offsetWidth - bar;
		var usedWidth = widthAvailable - requiredWidth;

		// Control column needs to always be included. This makes it sub-
		// optimal in terms of using the available with, but to stop layout
		// thrashing or overflow. Also we need to account for the control column
		// width first so we know how much width is available for the other
		// columns, since the control column might not be the first one shown
		for ( i=0, ien=display.length ; i<ien ; i++ ) {
			if ( columns[i].control ) {
				usedWidth -= columns[i].minWidth;
			}
		}

		// Allow columns to be shown (counting by priority and then right to
		// left) until we run out of room
		var empty = false;
		for ( i=0, ien=order.length ; i<ien ; i++ ) {
			var colIdx = order[i].columnIdx;

			if ( display[colIdx] === '-' && ! columns[colIdx].control && columns[colIdx].minWidth ) {
				// Once we've found a column that won't fit we don't let any
				// others display either, or columns might disappear in the
				// middle of the table
				if ( empty || usedWidth - columns[colIdx].minWidth < 0 ) {
					empty = true;
					display[colIdx] = false;
				}
				else {
					display[colIdx] = true;
				}

				usedWidth -= columns[colIdx].minWidth;
			}
		}

		// Determine if the 'control' column should be shown (if there is one).
		// This is the case when there is a hidden column (that is not the
		// control column). The two loops look inefficient here, but they are
		// trivial and will fly through. We need to know the outcome from the
		// first , before the action in the second can be taken
		var showControl = false;

		for ( i=0, ien=columns.length ; i<ien ; i++ ) {
			if ( ! columns[i].control && ! columns[i].never && ! display[i] ) {
				showControl = true;
				break;
			}
		}

		for ( i=0, ien=columns.length ; i<ien ; i++ ) {
			if ( columns[i].control ) {
				display[i] = showControl;
			}
		}

		// Finally we need to make sure that there is at least one column that
		// is visible
		if ( $.inArray( true, display ) === -1 ) {
			display[0] = true;
		}

		return display;
	},


	/**
	 * Create the internal `columns` array with information about the columns
	 * for the table. This includes determining which breakpoints the column
	 * will appear in, based upon class names in the column, which makes up the
	 * vast majority of this method.
	 *
	 * @private
	 */
	_classLogic: function ()
	{
		var that = this;
		var calc = {};
		var breakpoints = this.c.breakpoints;
		var dt = this.s.dt;
		var columns = dt.columns().eq(0).map( function (i) {
			var column = this.column(i);
			var className = column.header().className;
			var priority = dt.settings()[0].aoColumns[i].responsivePriority;

			if ( priority === undefined ) {
				var dataPriority = $(column.header()).data('priority');

				priority = dataPriority !== undefined ?
					dataPriority * 1 :
					10000;
			}

			return {
				className: className,
				includeIn: [],
				auto:      false,
				control:   false,
				never:     className.match(/\bnever\b/) ? true : false,
				priority:  priority
			};
		} );

		// Simply add a breakpoint to `includeIn` array, ensuring that there are
		// no duplicates
		var add = function ( colIdx, name ) {
			var includeIn = columns[ colIdx ].includeIn;

			if ( $.inArray( name, includeIn ) === -1 ) {
				includeIn.push( name );
			}
		};

		var column = function ( colIdx, name, operator, matched ) {
			var size, i, ien;

			if ( ! operator ) {
				columns[ colIdx ].includeIn.push( name );
			}
			else if ( operator === 'max-' ) {
				// Add this breakpoint and all smaller
				size = that._find( name ).width;

				for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {
					if ( breakpoints[i].width <= size ) {
						add( colIdx, breakpoints[i].name );
					}
				}
			}
			else if ( operator === 'min-' ) {
				// Add this breakpoint and all larger
				size = that._find( name ).width;

				for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {
					if ( breakpoints[i].width >= size ) {
						add( colIdx, breakpoints[i].name );
					}
				}
			}
			else if ( operator === 'not-' ) {
				// Add all but this breakpoint
				for ( i=0, ien=breakpoints.length ; i<ien ; i++ ) {
					if ( breakpoints[i].name.indexOf( matched ) === -1 ) {
						add( colIdx, breakpoints[i].name );
					}
				}
			}
		};

		// Loop over each column and determine if it has a responsive control
		// class
		columns.each( function ( col, i ) {
			var classNames = col.className.split(' ');
			var hasClass = false;

			// Split the class name up so multiple rules can be applied if needed
			for ( var k=0, ken=classNames.length ; k<ken ; k++ ) {
				var className = $.trim( classNames[k] );

				if ( className === 'all' ) {
					// Include in all
					hasClass = true;
					col.includeIn = $.map( breakpoints, function (a) {
						return a.name;
					} );
					return;
				}
				else if ( className === 'none' || col.never ) {
					// Include in none (default) and no auto
					hasClass = true;
					return;
				}
				else if ( className === 'control' ) {
					// Special column that is only visible, when one of the other
					// columns is hidden. This is used for the details control
					hasClass = true;
					col.control = true;
					return;
				}

				$.each( breakpoints, function ( j, breakpoint ) {
					// Does this column have a class that matches this breakpoint?
					var brokenPoint = breakpoint.name.split('-');
					var re = new RegExp( '(min\\-|max\\-|not\\-)?('+brokenPoint[0]+')(\\-[_a-zA-Z0-9])?' );
					var match = className.match( re );

					if ( match ) {
						hasClass = true;

						if ( match[2] === brokenPoint[0] && match[3] === '-'+brokenPoint[1] ) {
							// Class name matches breakpoint name fully
							column( i, breakpoint.name, match[1], match[2]+match[3] );
						}
						else if ( match[2] === brokenPoint[0] && ! match[3] ) {
							// Class name matched primary breakpoint name with no qualifier
							column( i, breakpoint.name, match[1], match[2] );
						}
					}
				} );
			}

			// If there was no control class, then automatic sizing is used
			if ( ! hasClass ) {
				col.auto = true;
			}
		} );

		this.s.columns = columns;
	},


	/**
	 * Show the details for the child row
	 *
	 * @param  {DataTables.Api} row    API instance for the row
	 * @param  {boolean}        update Update flag
	 * @private
	 */
	_detailsDisplay: function ( row, update )
	{
		var that = this;
		var dt = this.s.dt;
		var details = this.c.details;

		if ( details && details.type !== false ) {
			var res = details.display( row, update, function () {
				return details.renderer(
					dt, row[0], that._detailsObj(row[0])
				);
			} );

			if ( res === true || res === false ) {
				$(dt.table().node()).triggerHandler( 'responsive-display.dt', [dt, row, res, update] );
			}
		}
	},


	/**
	 * Initialisation for the details handler
	 *
	 * @private
	 */
	_detailsInit: function ()
	{
		var that    = this;
		var dt      = this.s.dt;
		var details = this.c.details;

		// The inline type always uses the first child as the target
		if ( details.type === 'inline' ) {
			details.target = 'td:first-child, th:first-child';
		}

		// Keyboard accessibility
		dt.on( 'draw.dtr', function () {
			that._tabIndexes();
		} );
		that._tabIndexes(); // Initial draw has already happened

		$( dt.table().body() ).on( 'keyup.dtr', 'td, th', function (e) {
			if ( e.keyCode === 13 && $(this).data('dtr-keyboard') ) {
				$(this).click();
			}
		} );

		// type.target can be a string jQuery selector or a column index
		var target   = details.target;
		var selector = typeof target === 'string' ? target : 'td, th';

		// Click handler to show / hide the details rows when they are available
		$( dt.table().body() )
			.on( 'click.dtr mousedown.dtr mouseup.dtr', selector, function (e) {
				// If the table is not collapsed (i.e. there is no hidden columns)
				// then take no action
				if ( ! $(dt.table().node()).hasClass('collapsed' ) ) {
					return;
				}

				// Check that the row is actually a DataTable's controlled node
				if ( ! dt.row( $(this).closest('tr') ).length ) {
					return;
				}

				// For column index, we determine if we should act or not in the
				// handler - otherwise it is already okay
				if ( typeof target === 'number' ) {
					var targetIdx = target < 0 ?
						dt.columns().eq(0).length + target :
						target;

					if ( dt.cell( this ).index().column !== targetIdx ) {
						return;
					}
				}

				// $().closest() includes itself in its check
				var row = dt.row( $(this).closest('tr') );

				// Check event type to do an action
				if ( e.type === 'click' ) {
					// The renderer is given as a function so the caller can execute it
					// only when they need (i.e. if hiding there is no point is running
					// the renderer)
					that._detailsDisplay( row, false );
				}
				else if ( e.type === 'mousedown' ) {
					// For mouse users, prevent the focus ring from showing
					$(this).css('outline', 'none');
				}
				else if ( e.type === 'mouseup' ) {
					// And then re-allow at the end of the click
					$(this).blur().css('outline', '');
				}
			} );
	},


	/**
	 * Get the details to pass to a renderer for a row
	 * @param  {int} rowIdx Row index
	 * @private
	 */
	_detailsObj: function ( rowIdx )
	{
		var that = this;
		var dt = this.s.dt;

		return $.map( this.s.columns, function( col, i ) {
			// Never and control columns should not be passed to the renderer
			if ( col.never || col.control ) {
				return;
			}

			return {
				title:       dt.settings()[0].aoColumns[ i ].sTitle,
				data:        dt.cell( rowIdx, i ).render( that.c.orthogonal ),
				hidden:      dt.column( i ).visible() && !that.s.current[ i ],
				columnIndex: i,
				rowIndex:    rowIdx
			};
		} );
	},


	/**
	 * Find a breakpoint object from a name
	 *
	 * @param  {string} name Breakpoint name to find
	 * @return {object}      Breakpoint description object
	 * @private
	 */
	_find: function ( name )
	{
		var breakpoints = this.c.breakpoints;

		for ( var i=0, ien=breakpoints.length ; i<ien ; i++ ) {
			if ( breakpoints[i].name === name ) {
				return breakpoints[i];
			}
		}
	},


	/**
	 * Re-create the contents of the child rows as the display has changed in
	 * some way.
	 *
	 * @private
	 */
	_redrawChildren: function ()
	{
		var that = this;
		var dt = this.s.dt;

		dt.rows( {page: 'current'} ).iterator( 'row', function ( settings, idx ) {
			var row = dt.row( idx );

			that._detailsDisplay( dt.row( idx ), true );
		} );
	},


	/**
	 * Alter the table display for a resized viewport. This involves first
	 * determining what breakpoint the window currently is in, getting the
	 * column visibilities to apply and then setting them.
	 *
	 * @private
	 */
	_resize: function ()
	{
		var that = this;
		var dt = this.s.dt;
		var width = $(window).width();
		var breakpoints = this.c.breakpoints;
		var breakpoint = breakpoints[0].name;
		var columns = this.s.columns;
		var i, ien;
		var oldVis = this.s.current.slice();

		// Determine what breakpoint we are currently at
		for ( i=breakpoints.length-1 ; i>=0 ; i-- ) {
			if ( width <= breakpoints[i].width ) {
				breakpoint = breakpoints[i].name;
				break;
			}
		}
		
		// Show the columns for that break point
		var columnsVis = this._columnsVisiblity( breakpoint );
		this.s.current = columnsVis;

		// Set the class before the column visibility is changed so event
		// listeners know what the state is. Need to determine if there are
		// any columns that are not visible but can be shown
		var collapsedClass = false;
		for ( i=0, ien=columns.length ; i<ien ; i++ ) {
			if ( columnsVis[i] === false && ! columns[i].never && ! columns[i].control ) {
				collapsedClass = true;
				break;
			}
		}

		$( dt.table().node() ).toggleClass( 'collapsed', collapsedClass );

		var changed = false;

		dt.columns().eq(0).each( function ( colIdx, i ) {
			if ( columnsVis[i] !== oldVis[i] ) {
				changed = true;
				that._setColumnVis( colIdx, columnsVis[i] );
			}
		} );

		if ( changed ) {
			this._redrawChildren();

			// Inform listeners of the change
			$(dt.table().node()).trigger( 'responsive-resize.dt', [dt, this.s.current] );
		}
	},


	/**
	 * Determine the width of each column in the table so the auto column hiding
	 * has that information to work with. This method is never going to be 100%
	 * perfect since column widths can change slightly per page, but without
	 * seriously compromising performance this is quite effective.
	 *
	 * @private
	 */
	_resizeAuto: function ()
	{
		var dt = this.s.dt;
		var columns = this.s.columns;

		// Are we allowed to do auto sizing?
		if ( ! this.c.auto ) {
			return;
		}

		// Are there any columns that actually need auto-sizing, or do they all
		// have classes defined
		if ( $.inArray( true, $.map( columns, function (c) { return c.auto; } ) ) === -1 ) {
			return;
		}

		// Clone the table with the current data in it
		var tableWidth   = dt.table().node().offsetWidth;
		var columnWidths = dt.columns;
		var clonedTable  = dt.table().node().cloneNode( false );
		var clonedHeader = $( dt.table().header().cloneNode( false ) ).appendTo( clonedTable );
		var clonedBody   = $( dt.table().body() ).clone( false, false ).empty().appendTo( clonedTable ); // use jQuery because of IE8

		// Header
		var headerCells = dt.columns()
			.header()
			.filter( function (idx) {
				return dt.column(idx).visible();
			} )
			.to$()
			.clone( false )
			.css( 'display', 'table-cell' );

		// Body rows - we don't need to take account of DataTables' column
		// visibility since we implement our own here (hence the `display` set)
		$(clonedBody)
			.append( $(dt.rows( { page: 'current' } ).nodes()).clone( false ) )
			.find( 'th, td' ).css( 'display', '' );

		// Footer
		var footer = dt.table().footer();
		if ( footer ) {
			var clonedFooter = $( footer.cloneNode( false ) ).appendTo( clonedTable );
			var footerCells = dt.columns()
				.footer()
				.filter( function (idx) {
					return dt.column(idx).visible();
				} )
				.to$()
				.clone( false )
				.css( 'display', 'table-cell' );

			$('<tr/>')
				.append( footerCells )
				.appendTo( clonedFooter );
		}

		$('<tr/>')
			.append( headerCells )
			.appendTo( clonedHeader );

		// In the inline case extra padding is applied to the first column to
		// give space for the show / hide icon. We need to use this in the
		// calculation
		if ( this.c.details.type === 'inline' ) {
			$(clonedTable).addClass( 'dtr-inline collapsed' );
		}
		
		// It is unsafe to insert elements with the same name into the DOM
		// multiple times. For example, cloning and inserting a checked radio
		// clears the chcecked state of the original radio.
		$( clonedTable ).find( '[name]' ).removeAttr( 'name' );
		
		var inserted = $('<div/>')
			.css( {
				width: 1,
				height: 1,
				overflow: 'hidden'
			} )
			.append( clonedTable );

		inserted.insertBefore( dt.table().node() );

		// The cloned header now contains the smallest that each column can be
		headerCells.each( function (i) {
			var idx = dt.column.index( 'fromVisible', i );
			columns[ idx ].minWidth =  this.offsetWidth || 0;
		} );

		inserted.remove();
	},

	/**
	 * Set a column's visibility.
	 *
	 * We don't use DataTables' column visibility controls in order to ensure
	 * that column visibility can Responsive can no-exist. Since only IE8+ is
	 * supported (and all evergreen browsers of course) the control of the
	 * display attribute works well.
	 *
	 * @param {integer} col      Column index
	 * @param {boolean} showHide Show or hide (true or false)
	 * @private
	 */
	_setColumnVis: function ( col, showHide )
	{
		var dt = this.s.dt;
		var display = showHide ? '' : 'none'; // empty string will remove the attr

		$( dt.column( col ).header() ).css( 'display', display );
		$( dt.column( col ).footer() ).css( 'display', display );
		dt.column( col ).nodes().to$().css( 'display', display );
	},


	/**
	 * Update the cell tab indexes for keyboard accessibility. This is called on
	 * every table draw - that is potentially inefficient, but also the least
	 * complex option given that column visibility can change on the fly. Its a
	 * shame user-focus was removed from CSS 3 UI, as it would have solved this
	 * issue with a single CSS statement.
	 *
	 * @private
	 */
	_tabIndexes: function ()
	{
		var dt = this.s.dt;
		var cells = dt.cells( { page: 'current' } ).nodes().to$();
		var ctx = dt.settings()[0];
		var target = this.c.details.target;

		cells.filter( '[data-dtr-keyboard]' ).removeData( '[data-dtr-keyboard]' );

		var selector = typeof target === 'number' ?
			':eq('+target+')' :
			target;

		$( selector, dt.rows( { page: 'current' } ).nodes() )
			.attr( 'tabIndex', ctx.iTabIndex )
			.data( 'dtr-keyboard', 1 );
	}
} );


/**
 * List of default breakpoints. Each item in the array is an object with two
 * properties:
 *
 * * `name` - the breakpoint name.
 * * `width` - the breakpoint width
 *
 * @name Responsive.breakpoints
 * @static
 */
Responsive.breakpoints = [
	{ name: 'desktop',  width: Infinity },
	{ name: 'tablet-l', width: 1024 },
	{ name: 'tablet-p', width: 768 },
	{ name: 'mobile-l', width: 480 },
	{ name: 'mobile-p', width: 320 }
];


/**
 * Display methods - functions which define how the hidden data should be shown
 * in the table.
 *
 * @namespace
 * @name Responsive.defaults
 * @static
 */
Responsive.display = {
	childRow: function ( row, update, render ) {
		if ( update ) {
			if ( $(row.node()).hasClass('parent') ) {
				row.child( render(), 'child' ).show();

				return true;
			}
		}
		else {
			if ( ! row.child.isShown()  ) {
				row.child( render(), 'child' ).show();
				$( row.node() ).addClass( 'parent' );

				return true;
			}
			else {
				row.child( false );
				$( row.node() ).removeClass( 'parent' );

				return false;
			}
		}
	},

	childRowImmediate: function ( row, update, render ) {
		if ( (! update && row.child.isShown()) || ! row.responsive.hasHidden() ) {
			// User interaction and the row is show, or nothing to show
			row.child( false );
			$( row.node() ).removeClass( 'parent' );

			return false;
		}
		else {
			// Display
			row.child( render(), 'child' ).show();
			$( row.node() ).addClass( 'parent' );

			return true;
		}
	},

	// This is a wrapper so the modal options for Bootstrap and jQuery UI can
	// have options passed into them. This specific one doesn't need to be a
	// function but it is for consistency in the `modal` name
	modal: function ( options ) {
		return function ( row, update, render ) {
			if ( ! update ) {
				// Show a modal
				var close = function () {
					modal.remove(); // will tidy events for us
					$(document).off( 'keypress.dtr' );
				};

				var modal = $('<div class="dtr-modal"/>')
					.append( $('<div class="dtr-modal-display"/>')
						.append( $('<div class="dtr-modal-content"/>')
							.append( render() )
						)
						.append( $('<div class="dtr-modal-close">&times;</div>' )
							.click( function () {
								close();
							} )
						)
					)
					.append( $('<div class="dtr-modal-background"/>')
						.click( function () {
							close();
						} )
					)
					.appendTo( 'body' );

				$(document).on( 'keyup.dtr', function (e) {
					if ( e.keyCode === 27 ) {
						e.stopPropagation();

						close();
					}
				} );
			}
			else {
				$('div.dtr-modal-content')
					.empty()
					.append( render() );
			}

			if ( options && options.header ) {
				$('div.dtr-modal-content').prepend(
					'<h2>'+options.header( row )+'</h2>'
				);
			}
		};
	}
};


/**
 * Display methods - functions which define how the hidden data should be shown
 * in the table.
 *
 * @namespace
 * @name Responsive.defaults
 * @static
 */
Responsive.renderer = {
	listHidden: function () {
		return function ( api, rowIdx, columns ) {
			var data = $.map( columns, function ( col ) {
				return col.hidden ?
					'<li data-dtr-index="'+col.columnIndex+'" data-dt-row="'+col.rowIndex+'" data-dt-column="'+col.columnIndex+'">'+
						'<span class="dtr-title">'+
							col.title+
						'</span> '+
						'<span class="dtr-data">'+
							col.data+
						'</span>'+
					'</li>' :
					'';
			} ).join('');

			return data ?
				$('<ul data-dtr-index="'+rowIdx+'"/>').append( data ) :
				false;
		}
	},

	tableAll: function ( options ) {
		options = $.extend( {
			tableClass: ''
		}, options );

		return function ( api, rowIdx, columns ) {
			var data = $.map( columns, function ( col ) {
				return '<tr data-dt-row="'+col.rowIndex+'" data-dt-column="'+col.columnIndex+'">'+
						'<td>'+col.title+':'+'</td> '+
						'<td>'+col.data+'</td>'+
					'</tr>';
			} ).join('');

			return $('<table class="'+options.tableClass+'" width="100%"/>').append( data );
		}
	}
};

/**
 * Responsive default settings for initialisation
 *
 * @namespace
 * @name Responsive.defaults
 * @static
 */
Responsive.defaults = {
	/**
	 * List of breakpoints for the instance. Note that this means that each
	 * instance can have its own breakpoints. Additionally, the breakpoints
	 * cannot be changed once an instance has been creased.
	 *
	 * @type {Array}
	 * @default Takes the value of `Responsive.breakpoints`
	 */
	breakpoints: Responsive.breakpoints,

	/**
	 * Enable / disable auto hiding calculations. It can help to increase
	 * performance slightly if you disable this option, but all columns would
	 * need to have breakpoint classes assigned to them
	 *
	 * @type {Boolean}
	 * @default  `true`
	 */
	auto: true,

	/**
	 * Details control. If given as a string value, the `type` property of the
	 * default object is set to that value, and the defaults used for the rest
	 * of the object - this is for ease of implementation.
	 *
	 * The object consists of the following properties:
	 *
	 * * `display` - A function that is used to show and hide the hidden details
	 * * `renderer` - function that is called for display of the child row data.
	 *   The default function will show the data from the hidden columns
	 * * `target` - Used as the selector for what objects to attach the child
	 *   open / close to
	 * * `type` - `false` to disable the details display, `inline` or `column`
	 *   for the two control types
	 *
	 * @type {Object|string}
	 */
	details: {
		display: Responsive.display.childRow,

		renderer: Responsive.renderer.listHidden(),

		target: 0,

		type: 'inline'
	},

	/**
	 * Orthogonal data request option. This is used to define the data type
	 * requested when Responsive gets the data to show in the child row.
	 *
	 * @type {String}
	 */
	orthogonal: 'display'
};


/*
 * API
 */
var Api = $.fn.dataTable.Api;

// Doesn't do anything - work around for a bug in DT... Not documented
Api.register( 'responsive()', function () {
	return this;
} );

Api.register( 'responsive.index()', function ( li ) {
	li = $(li);

	return {
		column: li.data('dtr-index'),
		row:    li.parent().data('dtr-index')
	};
} );

Api.register( 'responsive.rebuild()', function () {
	return this.iterator( 'table', function ( ctx ) {
		if ( ctx._responsive ) {
			ctx._responsive._classLogic();
		}
	} );
} );

Api.register( 'responsive.recalc()', function () {
	return this.iterator( 'table', function ( ctx ) {
		if ( ctx._responsive ) {
			ctx._responsive._resizeAuto();
			ctx._responsive._resize();
		}
	} );
} );

Api.register( 'responsive.hasHidden()', function () {
	var ctx = this.context[0];

	return ctx._responsive ?
		$.inArray( false, ctx._responsive.s.current ) !== -1 :
		false;
} );


/**
 * Version information
 *
 * @name Responsive.version
 * @static
 */
Responsive.version = '2.1.0';


$.fn.dataTable.Responsive = Responsive;
$.fn.DataTable.Responsive = Responsive;

// Attach a listener to the document which listens for DataTables initialisation
// events so we can automatically initialise
$(document).on( 'preInit.dt.dtr', function (e, settings, json) {
	if ( e.namespace !== 'dt' ) {
		return;
	}

	if ( $(settings.nTable).hasClass( 'responsive' ) ||
		 $(settings.nTable).hasClass( 'dt-responsive' ) ||
		 settings.oInit.responsive ||
		 DataTable.defaults.responsive
	) {
		var init = settings.oInit.responsive;

		if ( init !== false ) {
			new Responsive( settings, $.isPlainObject( init ) ? init : {}  );
		}
	}
} );


return Responsive;
}));


/*! RowReorder 1.1.2
 * 2015-2016 SpryMedia Ltd - datatables.net/license
 */

/**
 * @summary     RowReorder
 * @description Row reordering extension for DataTables
 * @version     1.1.2
 * @file        dataTables.rowReorder.js
 * @author      SpryMedia Ltd (www.sprymedia.co.uk)
 * @contact     www.sprymedia.co.uk/contact
 * @copyright   Copyright 2015-2016 SpryMedia Ltd.
 *
 * This source file is free software, available under the following license:
 *   MIT license - http://datatables.net/license/mit
 *
 * This source file is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
 *
 * For details please refer to: http://www.datatables.net
 */

(function( factory ){
	if ( typeof define === 'function' && define.amd ) {
		// AMD
		define( ['jquery', 'datatables.net'], function ( $ ) {
			return factory( $, window, document );
		} );
	}
	else if ( typeof exports === 'object' ) {
		// CommonJS
		module.exports = function (root, $) {
			if ( ! root ) {
				root = window;
			}

			if ( ! $ || ! $.fn.dataTable ) {
				$ = require('datatables.net')(root, $).$;
			}

			return factory( $, root, root.document );
		};
	}
	else {
		// Browser
		factory( jQuery, window, document );
	}
}(function( $, window, document, undefined ) {
'use strict';
var DataTable = $.fn.dataTable;


/**
 * RowReorder provides the ability in DataTables to click and drag rows to
 * reorder them. When a row is dropped the data for the rows effected will be
 * updated to reflect the change. Normally this data point should also be the
 * column being sorted upon in the DataTable but this does not need to be the
 * case. RowReorder implements a "data swap" method - so the rows being
 * reordered take the value of the data point from the row that used to occupy
 * the row's new position.
 *
 * Initialisation is done by either:
 *
 * * `rowReorder` parameter in the DataTable initialisation object
 * * `new $.fn.dataTable.RowReorder( table, opts )` after DataTables
 *   initialisation.
 * 
 *  @class
 *  @param {object} settings DataTables settings object for the host table
 *  @param {object} [opts] Configuration options
 *  @requires jQuery 1.7+
 *  @requires DataTables 1.10.7+
 */
var RowReorder = function ( dt, opts ) {
	// Sanity check that we are using DataTables 1.10 or newer
	if ( ! DataTable.versionCheck || ! DataTable.versionCheck( '1.10.8' ) ) {
		throw 'DataTables RowReorder requires DataTables 1.10.8 or newer';
	}

	// User and defaults configuration object
	this.c = $.extend( true, {},
		DataTable.defaults.rowReorder,
		RowReorder.defaults,
		opts
	);

	// Internal settings
	this.s = {
		/** @type {integer} Scroll body top cache */
		bodyTop: null,

		/** @type {DataTable.Api} DataTables' API instance */
		dt: new DataTable.Api( dt ),

		/** @type {function} Data fetch function */
		getDataFn: DataTable.ext.oApi._fnGetObjectDataFn( this.c.dataSrc ),

		/** @type {array} Pixel positions for row insertion calculation */
		middles: null,

		/** @type {Object} Cached dimension information for use in the mouse move event handler */
		scroll: {},

		/** @type {integer} Interval object used for smooth scrolling */
		scrollInterval: null,

		/** @type {function} Data set function */
		setDataFn: DataTable.ext.oApi._fnSetObjectDataFn( this.c.dataSrc ),

		/** @type {Object} Mouse down information */
		start: {
			top: 0,
			left: 0,
			offsetTop: 0,
			offsetLeft: 0,
			nodes: []
		},

		/** @type {integer} Window height cached value */
		windowHeight: 0
	};

	// DOM items
	this.dom = {
		/** @type {jQuery} Cloned row being moved around */
		clone: null,

		/** @type {jQuery} DataTables scrolling container */
		dtScroll: $('div.dataTables_scrollBody', this.s.dt.table().container())
	};

	// Check if row reorder has already been initialised on this table
	var settings = this.s.dt.settings()[0];
	var exisiting = settings.rowreorder;
	if ( exisiting ) {
		return exisiting;
	}

	settings.rowreorder = this;
	this._constructor();
};


$.extend( RowReorder.prototype, {
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Constructor
	 */

	/**
	 * Initialise the RowReorder instance
	 *
	 * @private
	 */
	_constructor: function ()
	{
		var that = this;
		var dt = this.s.dt;
		var table = $( dt.table().node() );

		// Need to be able to calculate the row positions relative to the table
		if ( table.css('position') === 'static' ) {
			table.css( 'position', 'relative' );
		}

		// listen for mouse down on the target column - we have to implement
		// this rather than using HTML5 drag and drop as drag and drop doesn't
		// appear to work on table rows at this time. Also mobile browsers are
		// not supported.
		// Use `table().container()` rather than just the table node for IE8 -
		// otherwise it only works once...
		$(dt.table().container()).on( 'mousedown.rowReorder touchstart.rowReorder', this.c.selector, function (e) {
			var tr = $(this).closest('tr');

			// Double check that it is a DataTable row
			if ( dt.row( tr ).any() ) {
				that._mouseDown( e, tr );
				return false;
			}
		} );

		dt.on( 'destroy.rowReorder', function () {
			$(dt.table().container()).off( '.rowReorder' );
			dt.off( '.rowReorder' );
		} );
	},


	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Private methods
	 */
	
	/**
	 * Cache the measurements that RowReorder needs in the mouse move handler
	 * to attempt to speed things up, rather than reading from the DOM.
	 *
	 * @private
	 */
	_cachePositions: function ()
	{
		var dt = this.s.dt;

		// Frustratingly, if we add `position:relative` to the tbody, the
		// position is still relatively to the parent. So we need to adjust
		// for that
		var headerHeight = $( dt.table().node() ).find('thead').outerHeight();

		// Need to pass the nodes through jQuery to get them in document order,
		// not what DataTables thinks it is, since we have been altering the
		// order
		var nodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() );
		var tops = $.map( nodes, function ( node, i ) {
			return $(node).position().top - headerHeight;
		} );

		var middles = $.map( tops, function ( top, i ) {
			return tops.length < i-1 ?
				(top + tops[i+1]) / 2 :
				(top + top + $( dt.row( ':last-child' ).node() ).outerHeight() ) / 2;
		} );

		this.s.middles = middles;
		this.s.bodyTop = $( dt.table().body() ).offset().top;
		this.s.windowHeight = $(window).height();
	},


	/**
	 * Clone a row so it can be floated around the screen
	 *
	 * @param  {jQuery} target Node to be cloned
	 * @private
	 */
	_clone: function ( target )
	{
		var dt = this.s.dt;
		var clone = $( dt.table().node().cloneNode(false) )
			.addClass( 'dt-rowReorder-float' )
			.append('<tbody/>')
			.append( target.clone( false ) );

		// Match the table and column widths - read all sizes before setting
		// to reduce reflows
		var tableWidth = target.outerWidth();
		var tableHeight = target.outerHeight();
		var sizes = target.children().map( function () {
			return $(this).width();
		} );

		clone
			.width( tableWidth )
			.height( tableHeight )
			.find('tr').children().each( function (i) {
				this.style.width = sizes[i]+'px';
			} );

		// Insert into the document to have it floating around
		clone.appendTo( 'body' );

		this.dom.clone = clone;
	},


	/**
	 * Update the cloned item's position in the document
	 *
	 * @param  {object} e Event giving the mouse's position
	 * @private
	 */
	_clonePosition: function ( e )
	{
		var start = this.s.start;
		var topDiff = this._eventToPage( e, 'Y' ) - start.top;
		var leftDiff = this._eventToPage( e, 'X' ) - start.left;
		var snap = this.c.snapX;
		var left;

		if ( snap === true ) {
			left = start.offsetLeft;
		}
		else if ( typeof snap === 'number' ) {
			left = start.offsetLeft + snap;
		}
		else {
			left = leftDiff + start.offsetLeft;
		}

		this.dom.clone.css( {
			top: topDiff + start.offsetTop,
			left: left
		} );
	},


	/**
	 * Emit an event on the DataTable for listeners
	 *
	 * @param  {string} name Event name
	 * @param  {array} args Event arguments
	 * @private
	 */
	_emitEvent: function ( name, args )
	{
		this.s.dt.iterator( 'table', function ( ctx, i ) {
			$(ctx.nTable).triggerHandler( name+'.dt', args );
		} );
	},


	/**
	 * Get pageX/Y position from an event, regardless of if it is a mouse or
	 * touch event.
	 *
	 * @param  {object} e Event
	 * @param  {string} pos X or Y (must be a capital)
	 * @private
	 */
	_eventToPage: function ( e, pos )
	{
		if ( e.type.indexOf( 'touch' ) !== -1 ) {
			return e.originalEvent.touches[0][ 'page'+pos ];
		}

		return e[ 'page'+pos ];
	},


	/**
	 * Mouse down event handler. Read initial positions and add event handlers
	 * for the move.
	 *
	 * @param  {object} e      Mouse event
	 * @param  {jQuery} target TR element that is to be moved
	 * @private
	 */
	_mouseDown: function ( e, target )
	{
		var that = this;
		var dt = this.s.dt;
		var start = this.s.start;

		var offset = target.offset();
		start.top = this._eventToPage( e, 'Y' );
		start.left = this._eventToPage( e, 'X' );
		start.offsetTop = offset.top;
		start.offsetLeft = offset.left;
		start.nodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() );

		this._cachePositions();
		this._clone( target );
		this._clonePosition( e );

		this.dom.target = target;
		target.addClass( 'dt-rowReorder-moving' );

		$( document )
			.on( 'mouseup.rowReorder touchend.rowReorder', function (e) {
				that._mouseUp(e);
			} )
			.on( 'mousemove.rowReorder touchmove.rowReorder', function (e) {
				that._mouseMove(e);
			} );

		// Check if window is x-scrolling - if not, disable it for the duration
		// of the drag
		if ( $(window).width() === $(document).width() ) {
			$(document.body).addClass( 'dt-rowReorder-noOverflow' );
		}

		// Cache scrolling information so mouse move doesn't need to read.
		// This assumes that the window and DT scroller will not change size
		// during an row drag, which I think is a fair assumption
		var scrollWrapper = this.dom.dtScroll;
		this.s.scroll = {
			windowHeight: $(window).height(),
			windowWidth:  $(window).width(),
			dtTop:        scrollWrapper.length ? scrollWrapper.offset().top : null,
			dtLeft:       scrollWrapper.length ? scrollWrapper.offset().left : null,
			dtHeight:     scrollWrapper.length ? scrollWrapper.outerHeight() : null,
			dtWidth:      scrollWrapper.length ? scrollWrapper.outerWidth() : null
		};
	},


	/**
	 * Mouse move event handler - move the cloned row and shuffle the table's
	 * rows if required.
	 *
	 * @param  {object} e Mouse event
	 * @private
	 */
	_mouseMove: function ( e )
	{
		this._clonePosition( e );

		// Transform the mouse position into a position in the table's body
		var bodyY = this._eventToPage( e, 'Y' ) - this.s.bodyTop;
		var middles = this.s.middles;
		var insertPoint = null;
		var dt = this.s.dt;
		var body = dt.table().body();

		// Determine where the row should be inserted based on the mouse
		// position
		for ( var i=0, ien=middles.length ; i<ien ; i++ ) {
			if ( bodyY < middles[i] ) {
				insertPoint = i;
				break;
			}
		}

		if ( insertPoint === null ) {
			insertPoint = middles.length;
		}

		// Perform the DOM shuffle if it has changed from last time
		if ( this.s.lastInsert === null || this.s.lastInsert !== insertPoint ) {
			if ( insertPoint === 0 ) {
				this.dom.target.prependTo( body );
			}
			else {
				var nodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() );

				if ( insertPoint > this.s.lastInsert ) {
					this.dom.target.insertAfter( nodes[ insertPoint-1 ] );
				}
				else {
					this.dom.target.insertBefore( nodes[ insertPoint ] );
				}
			}

			this._cachePositions();

			this.s.lastInsert = insertPoint;
		}

		this._shiftScroll( e );
	},


	/**
	 * Mouse up event handler - release the event handlers and perform the
	 * table updates
	 *
	 * @param  {object} e Mouse event
	 * @private
	 */
	_mouseUp: function ( e )
	{
		var dt = this.s.dt;
		var i, ien;
		var dataSrc = this.c.dataSrc;

		this.dom.clone.remove();
		this.dom.clone = null;

		this.dom.target.removeClass( 'dt-rowReorder-moving' );
		//this.dom.target = null;

		$(document).off( '.rowReorder' );
		$(document.body).removeClass( 'dt-rowReorder-noOverflow' );

		clearInterval( this.s.scrollInterval );
		this.s.scrollInterval = null;

		// Calculate the difference
		var startNodes = this.s.start.nodes;
		var endNodes = $.unique( dt.rows( { page: 'current' } ).nodes().toArray() );
		var idDiff = {};
		var fullDiff = [];
		var diffNodes = [];
		var getDataFn = this.s.getDataFn;
		var setDataFn = this.s.setDataFn;

		for ( i=0, ien=startNodes.length ; i<ien ; i++ ) {
			if ( startNodes[i] !== endNodes[i] ) {
				var id = dt.row( endNodes[i] ).id();
				var endRowData = dt.row( endNodes[i] ).data();
				var startRowData = dt.row( startNodes[i] ).data();

				if ( id ) {
					idDiff[ id ] = getDataFn( startRowData );
				}

				fullDiff.push( {
					node: endNodes[i],
					oldData: getDataFn( endRowData ),
					newData: getDataFn( startRowData ),
					newPosition: i,
					oldPosition: $.inArray( endNodes[i], startNodes )
				} );

				diffNodes.push( endNodes[i] );
			}
		}
		
		// Create event args
		var eventArgs = [ fullDiff, {
			dataSrc:    dataSrc,
			nodes:      diffNodes,
			values:     idDiff,
			triggerRow: dt.row( this.dom.target )
		} ];
		
		// Emit event
		this._emitEvent( 'row-reorder', eventArgs );

		// Editor interface
		if ( this.c.editor ) {
			this.c.editor
				.edit( diffNodes, false, {
					submit: 'changed'
				} )
				.multiSet( dataSrc, idDiff )
				.submit();
		}

		// Do update if required
		if ( this.c.update ) {
			for ( i=0, ien=fullDiff.length ; i<ien ; i++ ) {
				var row = dt.row( fullDiff[i].node );
				var rowData = row.data();

				setDataFn( rowData, fullDiff[i].newData );

				// Invalidate the cell that has the same data source as the dataSrc
				dt.columns().every( function () {
					if ( this.dataSrc() === dataSrc ) {
						dt.cell( fullDiff[i].node, this.index() ).invalidate( 'data' );
					}
				} );
			}
			
			// Trigger row reordered event
			this._emitEvent( 'row-reordered', eventArgs );

			dt.draw( false );
		}
	},


	/**
	 * Move the window and DataTables scrolling during a drag to scroll new
	 * content into view.
	 *
	 * This matches the `_shiftScroll` method used in AutoFill, but only
	 * horizontal scrolling is considered here.
	 *
	 * @param  {object} e Mouse move event object
	 * @private
	 */
	_shiftScroll: function ( e )
	{
		var that = this;
		var dt = this.s.dt;
		var scroll = this.s.scroll;
		var runInterval = false;
		var scrollSpeed = 5;
		var buffer = 65;
		var
			windowY = e.pageY - document.body.scrollTop,
			windowVert,
			dtVert;

		// Window calculations - based on the mouse position in the window,
		// regardless of scrolling
		if ( windowY < buffer ) {
			windowVert = scrollSpeed * -1;
		}
		else if ( windowY > scroll.windowHeight - buffer ) {
			windowVert = scrollSpeed;
		}

		// DataTables scrolling calculations - based on the table's position in
		// the document and the mouse position on the page
		if ( scroll.dtTop !== null && e.pageY < scroll.dtTop + buffer ) {
			dtVert = scrollSpeed * -1;
		}
		else if ( scroll.dtTop !== null && e.pageY > scroll.dtTop + scroll.dtHeight - buffer ) {
			dtVert = scrollSpeed;
		}

		// This is where it gets interesting. We want to continue scrolling
		// without requiring a mouse move, so we need an interval to be
		// triggered. The interval should continue until it is no longer needed,
		// but it must also use the latest scroll commands (for example consider
		// that the mouse might move from scrolling up to scrolling left, all
		// with the same interval running. We use the `scroll` object to "pass"
		// this information to the interval. Can't use local variables as they
		// wouldn't be the ones that are used by an already existing interval!
		if ( windowVert || dtVert ) {
			scroll.windowVert = windowVert;
			scroll.dtVert = dtVert;
			runInterval = true;
		}
		else if ( this.s.scrollInterval ) {
			// Don't need to scroll - remove any existing timer
			clearInterval( this.s.scrollInterval );
			this.s.scrollInterval = null;
		}

		// If we need to run the interval to scroll and there is no existing
		// interval (if there is an existing one, it will continue to run)
		if ( ! this.s.scrollInterval && runInterval ) {
			this.s.scrollInterval = setInterval( function () {
				// Don't need to worry about setting scroll <0 or beyond the
				// scroll bound as the browser will just reject that.
				if ( scroll.windowVert ) {
					document.body.scrollTop += scroll.windowVert;
				}

				// DataTables scrolling
				if ( scroll.dtVert ) {
					var scroller = that.dom.dtScroll[0];

					if ( scroll.dtVert ) {
						scroller.scrollTop += scroll.dtVert;
					}
				}
			}, 20 );
		}
	}
} );



/**
 * RowReorder default settings for initialisation
 *
 * @namespace
 * @name RowReorder.defaults
 * @static
 */
RowReorder.defaults = {
	/**
	 * Data point in the host row's data source object for where to get and set
	 * the data to reorder. This will normally also be the sorting column.
	 *
	 * @type {Number}
	 */
	dataSrc: 0,

	/**
	 * Editor instance that will be used to perform the update
	 *
	 * @type {DataTable.Editor}
	 */
	editor: null,

	/**
	 * Drag handle selector. This defines the element that when dragged will
	 * reorder a row.
	 *
	 * @type {String}
	 */
	selector: 'td:first-child',

	/**
	 * Optionally lock the dragged row's x-position. This can be `true` to
	 * fix the position match the host table's, `false` to allow free movement
	 * of the row, or a number to define an offset from the host table.
	 *
	 * @type {Boolean|number}
	 */
	snapX: false,

	/**
	 * Update the table's data on drop
	 *
	 * @type {Boolean}
	 */
	update: true
};


/**
 * Version information
 *
 * @name RowReorder.version
 * @static
 */
RowReorder.version = '1.1.2';


$.fn.dataTable.RowReorder = RowReorder;
$.fn.DataTable.RowReorder = RowReorder;

// Attach a listener to the document which listens for DataTables initialisation
// events so we can automatically initialise
$(document).on( 'init.dt.dtr', function (e, settings, json) {
	if ( e.namespace !== 'dt' ) {
		return;
	}

	var init = settings.oInit.rowReorder;
	var defaults = DataTable.defaults.rowReorder;

	if ( init || defaults ) {
		var opts = $.extend( {}, init, defaults );

		if ( init !== false ) {
			new RowReorder( settings, opts  );
		}
	}
} );


return RowReorder;
}));


/*! Scroller 1.4.2
 * ©2011-2016 SpryMedia Ltd - datatables.net/license
 */

/**
 * @summary     Scroller
 * @description Virtual rendering for DataTables
 * @version     1.4.2
 * @file        dataTables.scroller.js
 * @author      SpryMedia Ltd (www.sprymedia.co.uk)
 * @contact     www.sprymedia.co.uk/contact
 * @copyright   Copyright 2011-2016 SpryMedia Ltd.
 *
 * This source file is free software, available under the following license:
 *   MIT license - http://datatables.net/license/mit
 *
 * This source file is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
 *
 * For details please refer to: http://www.datatables.net
 */

(function( factory ){
	if ( typeof define === 'function' && define.amd ) {
		// AMD
		define( ['jquery', 'datatables.net'], function ( $ ) {
			return factory( $, window, document );
		} );
	}
	else if ( typeof exports === 'object' ) {
		// CommonJS
		module.exports = function (root, $) {
			if ( ! root ) {
				root = window;
			}

			if ( ! $ || ! $.fn.dataTable ) {
				$ = require('datatables.net')(root, $).$;
			}

			return factory( $, root, root.document );
		};
	}
	else {
		// Browser
		factory( jQuery, window, document );
	}
}(function( $, window, document, undefined ) {
'use strict';
var DataTable = $.fn.dataTable;


/**
 * Scroller is a virtual rendering plug-in for DataTables which allows large
 * datasets to be drawn on screen every quickly. What the virtual rendering means
 * is that only the visible portion of the table (and a bit to either side to make
 * the scrolling smooth) is drawn, while the scrolling container gives the
 * visual impression that the whole table is visible. This is done by making use
 * of the pagination abilities of DataTables and moving the table around in the
 * scrolling container DataTables adds to the page. The scrolling container is
 * forced to the height it would be for the full table display using an extra
 * element.
 *
 * Note that rows in the table MUST all be the same height. Information in a cell
 * which expands on to multiple lines will cause some odd behaviour in the scrolling.
 *
 * Scroller is initialised by simply including the letter 'S' in the sDom for the
 * table you want to have this feature enabled on. Note that the 'S' must come
 * AFTER the 't' parameter in `dom`.
 *
 * Key features include:
 *   <ul class="limit_length">
 *     <li>Speed! The aim of Scroller for DataTables is to make rendering large data sets fast</li>
 *     <li>Full compatibility with deferred rendering in DataTables for maximum speed</li>
 *     <li>Display millions of rows</li>
 *     <li>Integration with state saving in DataTables (scrolling position is saved)</li>
 *     <li>Easy to use</li>
 *   </ul>
 *
 *  @class
 *  @constructor
 *  @global
 *  @param {object} dt DataTables settings object or API instance
 *  @param {object} [opts={}] Configuration object for FixedColumns. Options 
 *    are defined by {@link Scroller.defaults}
 *
 *  @requires jQuery 1.7+
 *  @requires DataTables 1.10.0+
 *
 *  @example
 *    $(document).ready(function() {
 *        $('#example').DataTable( {
 *            "scrollY": "200px",
 *            "ajax": "media/dataset/large.txt",
 *            "dom": "frtiS",
 *            "deferRender": true
 *        } );
 *    } );
 */
var Scroller = function ( dt, opts ) {
	/* Sanity check - you just know it will happen */
	if ( ! (this instanceof Scroller) ) {
		alert( "Scroller warning: Scroller must be initialised with the 'new' keyword." );
		return;
	}

	if ( opts === undefined ) {
		opts = {};
	}

	/**
	 * Settings object which contains customisable information for the Scroller instance
	 * @namespace
	 * @private
	 * @extends Scroller.defaults
	 */
	this.s = {
		/**
		 * DataTables settings object
		 *  @type     object
		 *  @default  Passed in as first parameter to constructor
		 */
		"dt": $.fn.dataTable.Api( dt ).settings()[0],

		/**
		 * Pixel location of the top of the drawn table in the viewport
		 *  @type     int
		 *  @default  0
		 */
		"tableTop": 0,

		/**
		 * Pixel location of the bottom of the drawn table in the viewport
		 *  @type     int
		 *  @default  0
		 */
		"tableBottom": 0,

		/**
		 * Pixel location of the boundary for when the next data set should be loaded and drawn
		 * when scrolling up the way.
		 *  @type     int
		 *  @default  0
		 *  @private
		 */
		"redrawTop": 0,

		/**
		 * Pixel location of the boundary for when the next data set should be loaded and drawn
		 * when scrolling down the way. Note that this is actually calculated as the offset from
		 * the top.
		 *  @type     int
		 *  @default  0
		 *  @private
		 */
		"redrawBottom": 0,

		/**
		 * Auto row height or not indicator
		 *  @type     bool
		 *  @default  0
		 */
		"autoHeight": true,

		/**
		 * Number of rows calculated as visible in the visible viewport
		 *  @type     int
		 *  @default  0
		 */
		"viewportRows": 0,

		/**
		 * setTimeout reference for state saving, used when state saving is enabled in the DataTable
		 * and when the user scrolls the viewport in order to stop the cookie set taking too much
		 * CPU!
		 *  @type     int
		 *  @default  0
		 */
		"stateTO": null,

		/**
		 * setTimeout reference for the redraw, used when server-side processing is enabled in the
		 * DataTables in order to prevent DoSing the server
		 *  @type     int
		 *  @default  null
		 */
		"drawTO": null,

		heights: {
			jump: null,
			page: null,
			virtual: null,
			scroll: null,

			/**
			 * Height of rows in the table
			 *  @type     int
			 *  @default  0
			 */
			row: null,

			/**
			 * Pixel height of the viewport
			 *  @type     int
			 *  @default  0
			 */
			viewport: null
		},

		topRowFloat: 0,
		scrollDrawDiff: null,
		loaderVisible: false
	};

	// @todo The defaults should extend a `c` property and the internal settings
	// only held in the `s` property. At the moment they are mixed
	this.s = $.extend( this.s, Scroller.oDefaults, opts );

	// Workaround for row height being read from height object (see above comment)
	this.s.heights.row = this.s.rowHeight;

	/**
	 * DOM elements used by the class instance
	 * @private
	 * @namespace
	 *
	 */
	this.dom = {
		"force":    document.createElement('div'),
		"scroller": null,
		"table":    null,
		"loader":   null
	};

	// Attach the instance to the DataTables instance so it can be accessed in
	// future. Don't initialise Scroller twice on the same table
	if ( this.s.dt.oScroller ) {
		return;
	}

	this.s.dt.oScroller = this;

	/* Let's do it */
	this._fnConstruct();
};



$.extend( Scroller.prototype, {
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Public methods
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

	/**
	 * Calculate the pixel position from the top of the scrolling container for
	 * a given row
	 *  @param {int} iRow Row number to calculate the position of
	 *  @returns {int} Pixels
	 *  @example
	 *    $(document).ready(function() {
	 *      $('#example').dataTable( {
	 *        "sScrollY": "200px",
	 *        "sAjaxSource": "media/dataset/large.txt",
	 *        "sDom": "frtiS",
	 *        "bDeferRender": true,
	 *        "fnInitComplete": function (o) {
	 *          // Find where row 25 is
	 *          alert( o.oScroller.fnRowToPixels( 25 ) );
	 *        }
	 *      } );
	 *    } );
	 */
	"fnRowToPixels": function ( rowIdx, intParse, virtual )
	{
		var pixels;

		if ( virtual ) {
			pixels = this._domain( 'virtualToPhysical', rowIdx * this.s.heights.row );
		}
		else {
			var diff = rowIdx - this.s.baseRowTop;
			pixels = this.s.baseScrollTop + (diff * this.s.heights.row);
		}

		return intParse || intParse === undefined ?
			parseInt( pixels, 10 ) :
			pixels;
	},


	/**
	 * Calculate the row number that will be found at the given pixel position
	 * (y-scroll).
	 *
	 * Please note that when the height of the full table exceeds 1 million
	 * pixels, Scroller switches into a non-linear mode for the scrollbar to fit
	 * all of the records into a finite area, but this function returns a linear
	 * value (relative to the last non-linear positioning).
	 *  @param {int} iPixels Offset from top to calculate the row number of
	 *  @param {int} [intParse=true] If an integer value should be returned
	 *  @param {int} [virtual=false] Perform the calculations in the virtual domain
	 *  @returns {int} Row index
	 *  @example
	 *    $(document).ready(function() {
	 *      $('#example').dataTable( {
	 *        "sScrollY": "200px",
	 *        "sAjaxSource": "media/dataset/large.txt",
	 *        "sDom": "frtiS",
	 *        "bDeferRender": true,
	 *        "fnInitComplete": function (o) {
	 *          // Find what row number is at 500px
	 *          alert( o.oScroller.fnPixelsToRow( 500 ) );
	 *        }
	 *      } );
	 *    } );
	 */
	"fnPixelsToRow": function ( pixels, intParse, virtual )
	{
		var diff = pixels - this.s.baseScrollTop;
		var row = virtual ?
			this._domain( 'physicalToVirtual', pixels ) / this.s.heights.row :
			( diff / this.s.heights.row ) + this.s.baseRowTop;

		return intParse || intParse === undefined ?
			parseInt( row, 10 ) :
			row;
	},


	/**
	 * Calculate the row number that will be found at the given pixel position (y-scroll)
	 *  @param {int} iRow Row index to scroll to
	 *  @param {bool} [bAnimate=true] Animate the transition or not
	 *  @returns {void}
	 *  @example
	 *    $(document).ready(function() {
	 *      $('#example').dataTable( {
	 *        "sScrollY": "200px",
	 *        "sAjaxSource": "media/dataset/large.txt",
	 *        "sDom": "frtiS",
	 *        "bDeferRender": true,
	 *        "fnInitComplete": function (o) {
	 *          // Immediately scroll to row 1000
	 *          o.oScroller.fnScrollToRow( 1000 );
	 *        }
	 *      } );
	 *     
	 *      // Sometime later on use the following to scroll to row 500...
	 *          var oSettings = $('#example').dataTable().fnSettings();
	 *      oSettings.oScroller.fnScrollToRow( 500 );
	 *    } );
	 */
	"fnScrollToRow": function ( iRow, bAnimate )
	{
		var that = this;
		var ani = false;
		var px = this.fnRowToPixels( iRow );

		// We need to know if the table will redraw or not before doing the
		// scroll. If it will not redraw, then we need to use the currently
		// displayed table, and scroll with the physical pixels. Otherwise, we
		// need to calculate the table's new position from the virtual
		// transform.
		var preRows = ((this.s.displayBuffer-1)/2) * this.s.viewportRows;
		var drawRow = iRow - preRows;
		if ( drawRow < 0 ) {
			drawRow = 0;
		}

		if ( (px > this.s.redrawBottom || px < this.s.redrawTop) && this.s.dt._iDisplayStart !== drawRow ) {
			ani = true;
			px = this.fnRowToPixels( iRow, false, true );
		}

		if ( typeof bAnimate == 'undefined' || bAnimate )
		{
			this.s.ani = ani;
			$(this.dom.scroller).animate( {
				"scrollTop": px
			}, function () {
				// This needs to happen after the animation has completed and
				// the final scroll event fired
				setTimeout( function () {
					that.s.ani = false;
				}, 25 );
			} );
		}
		else
		{
			$(this.dom.scroller).scrollTop( px );
		}
	},


	/**
	 * Calculate and store information about how many rows are to be displayed
	 * in the scrolling viewport, based on current dimensions in the browser's
	 * rendering. This can be particularly useful if the table is initially
	 * drawn in a hidden element - for example in a tab.
	 *  @param {bool} [bRedraw=true] Redraw the table automatically after the recalculation, with
	 *    the new dimensions forming the basis for the draw.
	 *  @returns {void}
	 *  @example
	 *    $(document).ready(function() {
	 *      // Make the example container hidden to throw off the browser's sizing
	 *      document.getElementById('container').style.display = "none";
	 *      var oTable = $('#example').dataTable( {
	 *        "sScrollY": "200px",
	 *        "sAjaxSource": "media/dataset/large.txt",
	 *        "sDom": "frtiS",
	 *        "bDeferRender": true,
	 *        "fnInitComplete": function (o) {
	 *          // Immediately scroll to row 1000
	 *          o.oScroller.fnScrollToRow( 1000 );
	 *        }
	 *      } );
	 *     
	 *      setTimeout( function () {
	 *        // Make the example container visible and recalculate the scroller sizes
	 *        document.getElementById('container').style.display = "block";
	 *        oTable.fnSettings().oScroller.fnMeasure();
	 *      }, 3000 );
	 */
	"fnMeasure": function ( bRedraw )
	{
		if ( this.s.autoHeight )
		{
			this._fnCalcRowHeight();
		}

		var heights = this.s.heights;

		if ( heights.row ) {
			heights.viewport = $(this.dom.scroller).height();
			this.s.viewportRows = parseInt( heights.viewport / heights.row, 10 )+1;
			this.s.dt._iDisplayLength = this.s.viewportRows * this.s.displayBuffer;
		}

		if ( bRedraw === undefined || bRedraw )
		{
			this.s.dt.oInstance.fnDraw( false );
		}
	},


	/**
	 * Get information about current displayed record range. This corresponds to
	 * the information usually displayed in the "Info" block of the table.
	 *
	 * @returns {object} info as an object:
	 *  {
	 *      start: {int}, // the 0-indexed record at the top of the viewport
	 *      end:   {int}, // the 0-indexed record at the bottom of the viewport
	 *  }
	*/
	"fnPageInfo": function()
	{
		var 
			dt = this.s.dt,
			iScrollTop = this.dom.scroller.scrollTop,
			iTotal = dt.fnRecordsDisplay(),
			iPossibleEnd = Math.ceil(this.fnPixelsToRow(iScrollTop + this.s.heights.viewport, false, this.s.ani));

		return {
			start: Math.floor(this.fnPixelsToRow(iScrollTop, false, this.s.ani)),
			end: iTotal < iPossibleEnd ? iTotal-1 : iPossibleEnd-1
		};
	},


	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Private methods (they are of course public in JS, but recommended as private)
	 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

	/**
	 * Initialisation for Scroller
	 *  @returns {void}
	 *  @private
	 */
	"_fnConstruct": function ()
	{
		var that = this;

		/* Sanity check */
		if ( !this.s.dt.oFeatures.bPaginate ) {
			this.s.dt.oApi._fnLog( this.s.dt, 0, 'Pagination must be enabled for Scroller' );
			return;
		}

		/* Insert a div element that we can use to force the DT scrolling container to
		 * the height that would be required if the whole table was being displayed
		 */
		this.dom.force.style.position = "relative";
		this.dom.force.style.top = "0px";
		this.dom.force.style.left = "0px";
		this.dom.force.style.width = "1px";

		this.dom.scroller = $('div.'+this.s.dt.oClasses.sScrollBody, this.s.dt.nTableWrapper)[0];
		this.dom.scroller.appendChild( this.dom.force );
		this.dom.scroller.style.position = "relative";

		this.dom.table = $('>table', this.dom.scroller)[0];
		this.dom.table.style.position = "absolute";
		this.dom.table.style.top = "0px";
		this.dom.table.style.left = "0px";

		// Add class to 'announce' that we are a Scroller table
		$(this.s.dt.nTableWrapper).addClass('DTS');

		// Add a 'loading' indicator
		if ( this.s.loadingIndicator )
		{
			this.dom.loader = $('<div class="dataTables_processing DTS_Loading">'+this.s.dt.oLanguage.sLoadingRecords+'</div>')
				.css('display', 'none');

			$(this.dom.scroller.parentNode)
				.css('position', 'relative')
				.append( this.dom.loader );
		}

		/* Initial size calculations */
		if ( this.s.heights.row && this.s.heights.row != 'auto' )
		{
			this.s.autoHeight = false;
		}
		this.fnMeasure( false );

		/* Scrolling callback to see if a page change is needed - use a throttled
		 * function for the save save callback so we aren't hitting it on every
		 * scroll
		 */
		this.s.ingnoreScroll = true;
		this.s.stateSaveThrottle = this.s.dt.oApi._fnThrottle( function () {
			that.s.dt.oApi._fnSaveState( that.s.dt );
		}, 500 );
		$(this.dom.scroller).on( 'scroll.DTS', function (e) {
			that._fnScroll.call( that );
		} );

		/* In iOS we catch the touchstart event in case the user tries to scroll
		 * while the display is already scrolling
		 */
		$(this.dom.scroller).on('touchstart.DTS', function () {
			that._fnScroll.call( that );
		} );

		/* Update the scroller when the DataTable is redrawn */
		this.s.dt.aoDrawCallback.push( {
			"fn": function () {
				if ( that.s.dt.bInitialised ) {
					that._fnDrawCallback.call( that );
				}
			},
			"sName": "Scroller"
		} );

		/* On resize, update the information element, since the number of rows shown might change */
		$(window).on( 'resize.DTS', function () {
			that.fnMeasure( false );
			that._fnInfo();
		} );

		/* Add a state saving parameter to the DT state saving so we can restore the exact
		 * position of the scrolling
		 */
		var initialStateSave = true;
		this.s.dt.oApi._fnCallbackReg( this.s.dt, 'aoStateSaveParams', function (oS, oData) {
			/* Set iScroller to saved scroll position on initialization.
			 */
			if(initialStateSave && that.s.dt.oLoadedState){
				oData.iScroller = that.s.dt.oLoadedState.iScroller;
				oData.iScrollerTopRow = that.s.dt.oLoadedState.iScrollerTopRow;
				initialStateSave = false;
			} else {
				oData.iScroller = that.dom.scroller.scrollTop;
				oData.iScrollerTopRow = that.s.topRowFloat;
			}
		}, "Scroller_State" );

		if ( this.s.dt.oLoadedState ) {
			this.s.topRowFloat = this.s.dt.oLoadedState.iScrollerTopRow || 0;
		}

		// Measure immediately. Scroller will have been added using preInit, so
		// we can reliably do this here. We could potentially also measure on
		// init complete, which would be useful for cases where the data is Ajax
		// loaded and longer than a single line.
		$(this.s.dt.nTable).one( 'init.dt', function () {
			that.fnMeasure();
		} );

		/* Destructor */
		this.s.dt.aoDestroyCallback.push( {
			"sName": "Scroller",
			"fn": function () {
				$(window).off( 'resize.DTS' );
				$(that.dom.scroller).off('touchstart.DTS scroll.DTS');
				$(that.s.dt.nTableWrapper).removeClass('DTS');
				$('div.DTS_Loading', that.dom.scroller.parentNode).remove();
				$(that.s.dt.nTable).off( 'init.dt' );

				that.dom.table.style.position = "";
				that.dom.table.style.top = "";
				that.dom.table.style.left = "";
			}
		} );
	},


	/**
	 * Scrolling function - fired whenever the scrolling position is changed.
	 * This method needs to use the stored values to see if the table should be
	 * redrawn as we are moving towards the end of the information that is
	 * currently drawn or not. If needed, then it will redraw the table based on
	 * the new position.
	 *  @returns {void}
	 *  @private
	 */
	"_fnScroll": function ()
	{
		var
			that = this,
			heights = this.s.heights,
			iScrollTop = this.dom.scroller.scrollTop,
			iTopRow;

		if ( this.s.skip ) {
			return;
		}

		if ( this.s.ingnoreScroll ) {
			return;
		}

		/* If the table has been sorted or filtered, then we use the redraw that
		 * DataTables as done, rather than performing our own
		 */
		if ( this.s.dt.bFiltered || this.s.dt.bSorted ) {
			this.s.lastScrollTop = 0;
			return;
		}

		/* Update the table's information display for what is now in the viewport */
		this._fnInfo();

		/* We don't want to state save on every scroll event - that's heavy
		 * handed, so use a timeout to update the state saving only when the
		 * scrolling has finished
		 */
		clearTimeout( this.s.stateTO );
		this.s.stateTO = setTimeout( function () {
			that.s.dt.oApi._fnSaveState( that.s.dt );
		}, 250 );

		/* Check if the scroll point is outside the trigger boundary which would required
		 * a DataTables redraw
		 */
		if ( iScrollTop < this.s.redrawTop || iScrollTop > this.s.redrawBottom ) {
			var preRows = Math.ceil( ((this.s.displayBuffer-1)/2) * this.s.viewportRows );

			if ( Math.abs( iScrollTop - this.s.lastScrollTop ) > heights.viewport || this.s.ani ) {
				iTopRow = parseInt(this._domain( 'physicalToVirtual', iScrollTop ) / heights.row, 10) - preRows;
				this.s.topRowFloat = this._domain( 'physicalToVirtual', iScrollTop ) / heights.row;
			}
			else {
				iTopRow = this.fnPixelsToRow( iScrollTop ) - preRows;
				this.s.topRowFloat = this.fnPixelsToRow( iScrollTop, false );
			}

			if ( iTopRow <= 0 ) {
				/* At the start of the table */
				iTopRow = 0;
			}
			else if ( iTopRow + this.s.dt._iDisplayLength > this.s.dt.fnRecordsDisplay() ) {
				/* At the end of the table */
				iTopRow = this.s.dt.fnRecordsDisplay() - this.s.dt._iDisplayLength;
				if ( iTopRow < 0 ) {
					iTopRow = 0;
				}
			}
			else if ( iTopRow % 2 !== 0 ) {
				// For the row-striping classes (odd/even) we want only to start
				// on evens otherwise the stripes will change between draws and
				// look rubbish
				iTopRow++;
			}

			if ( iTopRow != this.s.dt._iDisplayStart ) {
				/* Cache the new table position for quick lookups */
				this.s.tableTop = $(this.s.dt.nTable).offset().top;
				this.s.tableBottom = $(this.s.dt.nTable).height() + this.s.tableTop;

				var draw =  function () {
					if ( that.s.scrollDrawReq === null ) {
						that.s.scrollDrawReq = iScrollTop;
					}

					that.s.dt._iDisplayStart = iTopRow;
					that.s.dt.oApi._fnDraw( that.s.dt );
				};

				/* Do the DataTables redraw based on the calculated start point - note that when
				 * using server-side processing we introduce a small delay to not DoS the server...
				 */
				if ( this.s.dt.oFeatures.bServerSide ) {
					clearTimeout( this.s.drawTO );
					this.s.drawTO = setTimeout( draw, this.s.serverWait );
				}
				else {
					draw();
				}

				if ( this.dom.loader && ! this.s.loaderVisible ) {
					this.dom.loader.css( 'display', 'block' );
					this.s.loaderVisible = true;
				}
			}
		}
		else {
			this.s.topRowFloat = this._domain( 'physicalToVirtual', iScrollTop ) / heights.row;
		}

		this.s.lastScrollTop = iScrollTop;
		this.s.stateSaveThrottle();
	},


	/**
	 * Convert from one domain to another. The physical domain is the actual
	 * pixel count on the screen, while the virtual is if we had browsers which
	 * had scrolling containers of infinite height (i.e. the absolute value)
	 *
	 *  @param {string} dir Domain transform direction, `virtualToPhysical` or
	 *    `physicalToVirtual` 
	 *  @returns {number} Calculated transform
	 *  @private
	 */
	_domain: function ( dir, val )
	{
		var heights = this.s.heights;
		var coeff;

		// If the virtual and physical height match, then we use a linear
		// transform between the two, allowing the scrollbar to be linear
		if ( heights.virtual === heights.scroll ) {
			return val;
		}

		// Otherwise, we want a non-linear scrollbar to take account of the
		// redrawing regions at the start and end of the table, otherwise these
		// can stutter badly - on large tables 30px (for example) scroll might
		// be hundreds of rows, so the table would be redrawing every few px at
		// the start and end. Use a simple quadratic to stop this. It does mean
		// the scrollbar is non-linear, but with such massive data sets, the
		// scrollbar is going to be a best guess anyway
		var xMax = (heights.scroll - heights.viewport) / 2;
		var yMax = (heights.virtual - heights.viewport) / 2;

		coeff = yMax / ( xMax * xMax );

		if ( dir === 'virtualToPhysical' ) {
			if ( val < yMax ) {
				return Math.pow(val / coeff, 0.5);
			}
			else {
				val = (yMax*2) - val;
				return val < 0 ?
					heights.scroll :
					(xMax*2) - Math.pow(val / coeff, 0.5);
			}
		}
		else if ( dir === 'physicalToVirtual' ) {
			if ( val < xMax ) {
				return val * val * coeff;
			}
			else {
				val = (xMax*2) - val;
				return val < 0 ?
					heights.virtual :
					(yMax*2) - (val * val * coeff);
			}
		}
	},


	/**
	 * Draw callback function which is fired when the DataTable is redrawn. The main function of
	 * this method is to position the drawn table correctly the scrolling container for the rows
	 * that is displays as a result of the scrolling position.
	 *  @returns {void}
	 *  @private
	 */
	"_fnDrawCallback": function ()
	{
		var
			that = this,
			heights = this.s.heights,
			iScrollTop = this.dom.scroller.scrollTop,
			iActualScrollTop = iScrollTop,
			iScrollBottom = iScrollTop + heights.viewport,
			iTableHeight = $(this.s.dt.nTable).height(),
			displayStart = this.s.dt._iDisplayStart,
			displayLen = this.s.dt._iDisplayLength,
			displayEnd = this.s.dt.fnRecordsDisplay();

		// Disable the scroll event listener while we are updating the DOM
		this.s.skip = true;

		// Resize the scroll forcing element
		this._fnScrollForce();

		// Reposition the scrolling for the updated virtual position if needed
		if ( displayStart === 0 ) {
			// Linear calculation at the top of the table
			iScrollTop = this.s.topRowFloat * heights.row;
		}
		else if ( displayStart + displayLen >= displayEnd ) {
			// Linear calculation that the bottom as well
			iScrollTop = heights.scroll - ((displayEnd - this.s.topRowFloat) * heights.row);
		}
		else {
			// Domain scaled in the middle
			iScrollTop = this._domain( 'virtualToPhysical', this.s.topRowFloat * heights.row );
		}

		this.dom.scroller.scrollTop = iScrollTop;

		// Store positional information so positional calculations can be based
		// upon the current table draw position
		this.s.baseScrollTop = iScrollTop;
		this.s.baseRowTop = this.s.topRowFloat;

		// Position the table in the virtual scroller
		var tableTop = iScrollTop - ((this.s.topRowFloat - displayStart) * heights.row);
		if ( displayStart === 0 ) {
			tableTop = 0;
		}
		else if ( displayStart + displayLen >= displayEnd ) {
			tableTop = heights.scroll - iTableHeight;
		}

		this.dom.table.style.top = tableTop+'px';

		/* Cache some information for the scroller */
		this.s.tableTop = tableTop;
		this.s.tableBottom = iTableHeight + this.s.tableTop;

		// Calculate the boundaries for where a redraw will be triggered by the
		// scroll event listener
		var boundaryPx = (iScrollTop - this.s.tableTop) * this.s.boundaryScale;
		this.s.redrawTop = iScrollTop - boundaryPx;
		this.s.redrawBottom = iScrollTop + boundaryPx;

		this.s.skip = false;

		// Restore the scrolling position that was saved by DataTable's state
		// saving Note that this is done on the second draw when data is Ajax
		// sourced, and the first draw when DOM soured
		if ( this.s.dt.oFeatures.bStateSave && this.s.dt.oLoadedState !== null &&
			 typeof this.s.dt.oLoadedState.iScroller != 'undefined' )
		{
			// A quirk of DataTables is that the draw callback will occur on an
			// empty set if Ajax sourced, but not if server-side processing.
			var ajaxSourced = (this.s.dt.sAjaxSource || that.s.dt.ajax) && ! this.s.dt.oFeatures.bServerSide ?
				true :
				false;

			if ( ( ajaxSourced && this.s.dt.iDraw == 2) ||
			     (!ajaxSourced && this.s.dt.iDraw == 1) )
			{
				setTimeout( function () {
					$(that.dom.scroller).scrollTop( that.s.dt.oLoadedState.iScroller );
					that.s.redrawTop = that.s.dt.oLoadedState.iScroller - (heights.viewport/2);

					// In order to prevent layout thrashing we need another
					// small delay
					setTimeout( function () {
						that.s.ingnoreScroll = false;
					}, 0 );
				}, 0 );
			}
		}
		else {
			that.s.ingnoreScroll = false;
		}

		// Because of the order of the DT callbacks, the info update will
		// take precedence over the one we want here. So a 'thread' break is
		// needed.  Only add the thread break if bInfo is set
		if ( this.s.dt.oFeatures.bInfo ) {
			setTimeout( function () {
				that._fnInfo.call( that );
			}, 0 );
		}

		// Hide the loading indicator
		if ( this.dom.loader && this.s.loaderVisible ) {
			this.dom.loader.css( 'display', 'none' );
			this.s.loaderVisible = false;
		}
	},


	/**
	 * Force the scrolling container to have height beyond that of just the
	 * table that has been drawn so the user can scroll the whole data set.
	 *
	 * Note that if the calculated required scrolling height exceeds a maximum
	 * value (1 million pixels - hard-coded) the forcing element will be set
	 * only to that maximum value and virtual / physical domain transforms will
	 * be used to allow Scroller to display tables of any number of records.
	 *  @returns {void}
	 *  @private
	 */
	_fnScrollForce: function ()
	{
		var heights = this.s.heights;
		var max = 1000000;

		heights.virtual = heights.row * this.s.dt.fnRecordsDisplay();
		heights.scroll = heights.virtual;

		if ( heights.scroll > max ) {
			heights.scroll = max;
		}

		// Minimum height so there is always a row visible (the 'no rows found'
		// if reduced to zero filtering)
		this.dom.force.style.height = heights.scroll > this.s.heights.row ?
			heights.scroll+'px' :
			this.s.heights.row+'px';
	},


	/**
	 * Automatic calculation of table row height. This is just a little tricky here as using
	 * initialisation DataTables has tale the table out of the document, so we need to create
	 * a new table and insert it into the document, calculate the row height and then whip the
	 * table out.
	 *  @returns {void}
	 *  @private
	 */
	"_fnCalcRowHeight": function ()
	{
		var dt = this.s.dt;
		var origTable = dt.nTable;
		var nTable = origTable.cloneNode( false );
		var tbody = $('<tbody/>').appendTo( nTable );
		var container = $(
			'<div class="'+dt.oClasses.sWrapper+' DTS">'+
				'<div class="'+dt.oClasses.sScrollWrapper+'">'+
					'<div class="'+dt.oClasses.sScrollBody+'"></div>'+
				'</div>'+
			'</div>'
		);

		// Want 3 rows in the sizing table so :first-child and :last-child
		// CSS styles don't come into play - take the size of the middle row
		$('tbody tr:lt(4)', origTable).clone().appendTo( tbody );
		while( $('tr', tbody).length < 3 ) {
			tbody.append( '<tr><td>&nbsp;</td></tr>' );
		}

		$('div.'+dt.oClasses.sScrollBody, container).append( nTable );

		// If initialised using `dom`, use the holding element as the insert point
		var insertEl = this.s.dt.nHolding || origTable.parentNode;

		if ( ! $(insertEl).is(':visible') ) {
			insertEl = 'body';
		}

		container.appendTo( insertEl );
		this.s.heights.row = $('tr', tbody).eq(1).outerHeight();

		container.remove();
	},


	/**
	 * Update any information elements that are controlled by the DataTable based on the scrolling
	 * viewport and what rows are visible in it. This function basically acts in the same way as
	 * _fnUpdateInfo in DataTables, and effectively replaces that function.
	 *  @returns {void}
	 *  @private
	 */
	"_fnInfo": function ()
	{
		if ( !this.s.dt.oFeatures.bInfo )
		{
			return;
		}

		var
			dt = this.s.dt,
			language = dt.oLanguage,
			iScrollTop = this.dom.scroller.scrollTop,
			iStart = Math.floor( this.fnPixelsToRow(iScrollTop, false, this.s.ani)+1 ),
			iMax = dt.fnRecordsTotal(),
			iTotal = dt.fnRecordsDisplay(),
			iPossibleEnd = Math.ceil( this.fnPixelsToRow(iScrollTop+this.s.heights.viewport, false, this.s.ani) ),
			iEnd = iTotal < iPossibleEnd ? iTotal : iPossibleEnd,
			sStart = dt.fnFormatNumber( iStart ),
			sEnd = dt.fnFormatNumber( iEnd ),
			sMax = dt.fnFormatNumber( iMax ),
			sTotal = dt.fnFormatNumber( iTotal ),
			sOut;

		if ( dt.fnRecordsDisplay() === 0 &&
			   dt.fnRecordsDisplay() == dt.fnRecordsTotal() )
		{
			/* Empty record set */
			sOut = language.sInfoEmpty+ language.sInfoPostFix;
		}
		else if ( dt.fnRecordsDisplay() === 0 )
		{
			/* Empty record set after filtering */
			sOut = language.sInfoEmpty +' '+
				language.sInfoFiltered.replace('_MAX_', sMax)+
					language.sInfoPostFix;
		}
		else if ( dt.fnRecordsDisplay() == dt.fnRecordsTotal() )
		{
			/* Normal record set */
			sOut = language.sInfo.
					replace('_START_', sStart).
					replace('_END_',   sEnd).
					replace('_MAX_',   sMax).
					replace('_TOTAL_', sTotal)+
				language.sInfoPostFix;
		}
		else
		{
			/* Record set after filtering */
			sOut = language.sInfo.
					replace('_START_', sStart).
					replace('_END_',   sEnd).
					replace('_MAX_',   sMax).
					replace('_TOTAL_', sTotal) +' '+
				language.sInfoFiltered.replace(
					'_MAX_',
					dt.fnFormatNumber(dt.fnRecordsTotal())
				)+
				language.sInfoPostFix;
		}

		var callback = language.fnInfoCallback;
		if ( callback ) {
			sOut = callback.call( dt.oInstance,
				dt, iStart, iEnd, iMax, iTotal, sOut
			);
		}

		var n = dt.aanFeatures.i;
		if ( typeof n != 'undefined' )
		{
			for ( var i=0, iLen=n.length ; i<iLen ; i++ )
			{
				$(n[i]).html( sOut );
			}
		}

		// DT doesn't actually (yet) trigger this event, but it will in future
		$(dt.nTable).triggerHandler( 'info.dt' );
	}
} );



/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Statics
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */


/**
 * Scroller default settings for initialisation
 *  @namespace
 *  @name Scroller.defaults
 *  @static
 */
Scroller.defaults = /** @lends Scroller.defaults */{
	/**
	 * Indicate if Scroller show show trace information on the console or not. This can be
	 * useful when debugging Scroller or if just curious as to what it is doing, but should
	 * be turned off for production.
	 *  @type     bool
	 *  @default  false
	 *  @static
	 *  @example
	 *    var oTable = $('#example').dataTable( {
	 *        "sScrollY": "200px",
	 *        "sDom": "frtiS",
	 *        "bDeferRender": true,
	 *        "oScroller": {
	 *          "trace": true
	 *        }
	 *    } );
	 */
	"trace": false,

	/**
	 * Scroller will attempt to automatically calculate the height of rows for it's internal
	 * calculations. However the height that is used can be overridden using this parameter.
	 *  @type     int|string
	 *  @default  auto
	 *  @static
	 *  @example
	 *    var oTable = $('#example').dataTable( {
	 *        "sScrollY": "200px",
	 *        "sDom": "frtiS",
	 *        "bDeferRender": true,
	 *        "oScroller": {
	 *          "rowHeight": 30
	 *        }
	 *    } );
	 */
	"rowHeight": "auto",

	/**
	 * When using server-side processing, Scroller will wait a small amount of time to allow
	 * the scrolling to finish before requesting more data from the server. This prevents
	 * you from DoSing your own server! The wait time can be configured by this parameter.
	 *  @type     int
	 *  @default  200
	 *  @static
	 *  @example
	 *    var oTable = $('#example').dataTable( {
	 *        "sScrollY": "200px",
	 *        "sDom": "frtiS",
	 *        "bDeferRender": true,
	 *        "oScroller": {
	 *          "serverWait": 100
	 *        }
	 *    } );
	 */
	"serverWait": 200,

	/**
	 * The display buffer is what Scroller uses to calculate how many rows it should pre-fetch
	 * for scrolling. Scroller automatically adjusts DataTables' display length to pre-fetch
	 * rows that will be shown in "near scrolling" (i.e. just beyond the current display area).
	 * The value is based upon the number of rows that can be displayed in the viewport (i.e.
	 * a value of 1), and will apply the display range to records before before and after the
	 * current viewport - i.e. a factor of 3 will allow Scroller to pre-fetch 1 viewport's worth
	 * of rows before the current viewport, the current viewport's rows and 1 viewport's worth
	 * of rows after the current viewport. Adjusting this value can be useful for ensuring
	 * smooth scrolling based on your data set.
	 *  @type     int
	 *  @default  7
	 *  @static
	 *  @example
	 *    var oTable = $('#example').dataTable( {
	 *        "sScrollY": "200px",
	 *        "sDom": "frtiS",
	 *        "bDeferRender": true,
	 *        "oScroller": {
	 *          "displayBuffer": 10
	 *        }
	 *    } );
	 */
	"displayBuffer": 9,

	/**
	 * Scroller uses the boundary scaling factor to decide when to redraw the table - which it
	 * typically does before you reach the end of the currently loaded data set (in order to
	 * allow the data to look continuous to a user scrolling through the data). If given as 0
	 * then the table will be redrawn whenever the viewport is scrolled, while 1 would not
	 * redraw the table until the currently loaded data has all been shown. You will want
	 * something in the middle - the default factor of 0.5 is usually suitable.
	 *  @type     float
	 *  @default  0.5
	 *  @static
	 *  @example
	 *    var oTable = $('#example').dataTable( {
	 *        "sScrollY": "200px",
	 *        "sDom": "frtiS",
	 *        "bDeferRender": true,
	 *        "oScroller": {
	 *          "boundaryScale": 0.75
	 *        }
	 *    } );
	 */
	"boundaryScale": 0.5,

	/**
	 * Show (or not) the loading element in the background of the table. Note that you should
	 * include the dataTables.scroller.css file for this to be displayed correctly.
	 *  @type     boolean
	 *  @default  false
	 *  @static
	 *  @example
	 *    var oTable = $('#example').dataTable( {
	 *        "sScrollY": "200px",
	 *        "sDom": "frtiS",
	 *        "bDeferRender": true,
	 *        "oScroller": {
	 *          "loadingIndicator": true
	 *        }
	 *    } );
	 */
	"loadingIndicator": false
};

Scroller.oDefaults = Scroller.defaults;



/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Constants
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/**
 * Scroller version
 *  @type      String
 *  @default   See code
 *  @name      Scroller.version
 *  @static
 */
Scroller.version = "1.4.2";



/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Initialisation
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

// Legacy `dom` parameter initialisation support
if ( typeof $.fn.dataTable == "function" &&
     typeof $.fn.dataTableExt.fnVersionCheck == "function" &&
     $.fn.dataTableExt.fnVersionCheck('1.10.0') )
{
	$.fn.dataTableExt.aoFeatures.push( {
		"fnInit": function( oDTSettings ) {
			var init = oDTSettings.oInit;
			var opts = init.scroller || init.oScroller || {};
			
			new Scroller( oDTSettings, opts );
		},
		"cFeature": "S",
		"sFeature": "Scroller"
	} );
}
else
{
	alert( "Warning: Scroller requires DataTables 1.10.0 or greater - www.datatables.net/download");
}

// Attach a listener to the document which listens for DataTables initialisation
// events so we can automatically initialise
$(document).on( 'preInit.dt.dtscroller', function (e, settings) {
	if ( e.namespace !== 'dt' ) {
		return;
	}

	var init = settings.oInit.scroller;
	var defaults = DataTable.defaults.scroller;

	if ( init || defaults ) {
		var opts = $.extend( {}, init, defaults );

		if ( init !== false ) {
			new Scroller( settings, opts  );
		}
	}
} );


// Attach Scroller to DataTables so it can be accessed as an 'extra'
$.fn.dataTable.Scroller = Scroller;
$.fn.DataTable.Scroller = Scroller;


// DataTables 1.10 API method aliases
var Api = $.fn.dataTable.Api;

Api.register( 'scroller()', function () {
	return this;
} );

// Undocumented and deprecated - is it actually useful at all?
Api.register( 'scroller().rowToPixels()', function ( rowIdx, intParse, virtual ) {
	var ctx = this.context;

	if ( ctx.length && ctx[0].oScroller ) {
		return ctx[0].oScroller.fnRowToPixels( rowIdx, intParse, virtual );
	}
	// undefined
} );

// Undocumented and deprecated - is it actually useful at all?
Api.register( 'scroller().pixelsToRow()', function ( pixels, intParse, virtual ) {
	var ctx = this.context;

	if ( ctx.length && ctx[0].oScroller ) {
		return ctx[0].oScroller.fnPixelsToRow( pixels, intParse, virtual );
	}
	// undefined
} );

// Undocumented and deprecated - use `row().scrollTo()` instead
Api.register( 'scroller().scrollToRow()', function ( row, ani ) {
	this.iterator( 'table', function ( ctx ) {
		if ( ctx.oScroller ) {
			ctx.oScroller.fnScrollToRow( row, ani );
		}
	} );

	return this;
} );

Api.register( 'row().scrollTo()', function ( ani ) {
	var that = this;

	this.iterator( 'row', function ( ctx, rowIdx ) {
		if ( ctx.oScroller ) {
			var displayIdx = that
				.rows( { order: 'applied', search: 'applied' } )
				.indexes()
				.indexOf( rowIdx );

			ctx.oScroller.fnScrollToRow( displayIdx, ani );
		}
	} );

	return this;
} );

Api.register( 'scroller.measure()', function ( redraw ) {
	this.iterator( 'table', function ( ctx ) {
		if ( ctx.oScroller ) {
			ctx.oScroller.fnMeasure( redraw );
		}
	} );

	return this;
} );

Api.register( 'scroller.page()', function() {
	var ctx = this.context;

	if ( ctx.length && ctx[0].oScroller ) {
		return ctx[0].oScroller.fnPageInfo();
	}
	// undefined
} );

return Scroller;
}));


/*! Select for DataTables 1.2.0
 * 2015-2016 SpryMedia Ltd - datatables.net/license/mit
 */

/**
 * @summary     Select for DataTables
 * @description A collection of API methods, events and buttons for DataTables
 *   that provides selection options of the items in a DataTable
 * @version     1.2.0
 * @file        dataTables.select.js
 * @author      SpryMedia Ltd (www.sprymedia.co.uk)
 * @contact     datatables.net/forums
 * @copyright   Copyright 2015-2016 SpryMedia Ltd.
 *
 * This source file is free software, available under the following license:
 *   MIT license - http://datatables.net/license/mit
 *
 * This source file is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
 *
 * For details please refer to: http://www.datatables.net/extensions/select
 */
(function( factory ){
	if ( typeof define === 'function' && define.amd ) {
		// AMD
		define( ['jquery', 'datatables.net'], function ( $ ) {
			return factory( $, window, document );
		} );
	}
	else if ( typeof exports === 'object' ) {
		// CommonJS
		module.exports = function (root, $) {
			if ( ! root ) {
				root = window;
			}

			if ( ! $ || ! $.fn.dataTable ) {
				$ = require('datatables.net')(root, $).$;
			}

			return factory( $, root, root.document );
		};
	}
	else {
		// Browser
		factory( jQuery, window, document );
	}
}(function( $, window, document, undefined ) {
'use strict';
var DataTable = $.fn.dataTable;


// Version information for debugger
DataTable.select = {};

DataTable.select.version = '1.2.0';

DataTable.select.init = function ( dt ) {
	var ctx = dt.settings()[0];
	var init = ctx.oInit.select;
	var defaults = DataTable.defaults.select;
	var opts = init === undefined ?
		defaults :
		init;

	// Set defaults
	var items = 'row';
	var style = 'api';
	var blurable = false;
	var info = true;
	var selector = 'td, th';
	var className = 'selected';

	ctx._select = {};

	// Initialisation customisations
	if ( opts === true ) {
		style = 'os';
	}
	else if ( typeof opts === 'string' ) {
		style = opts;
	}
	else if ( $.isPlainObject( opts ) ) {
		if ( opts.blurable !== undefined ) {
			blurable = opts.blurable;
		}

		if ( opts.info !== undefined ) {
			info = opts.info;
		}

		if ( opts.items !== undefined ) {
			items = opts.items;
		}

		if ( opts.style !== undefined ) {
			style = opts.style;
		}

		if ( opts.selector !== undefined ) {
			selector = opts.selector;
		}

		if ( opts.className !== undefined ) {
			className = opts.className;
		}
	}

	dt.select.selector( selector );
	dt.select.items( items );
	dt.select.style( style );
	dt.select.blurable( blurable );
	dt.select.info( info );
	ctx._select.className = className;


	// Sort table based on selected rows. Requires Select Datatables extension
	$.fn.dataTable.ext.order['select-checkbox'] = function ( settings, col ) {
		return this.api().column( col, {order: 'index'} ).nodes().map( function ( td ) {
			if ( settings._select.items === 'row' ) {
				return $( td ).parent().hasClass( settings._select.className );
			} else if ( settings._select.items === 'cell' ) {
				return $( td ).hasClass( settings._select.className );
			}
			return false;
		});
	};

	// If the init options haven't enabled select, but there is a selectable
	// class name, then enable
	if ( $( dt.table().node() ).hasClass( 'selectable' ) ) {
		dt.select.style( 'os' );
	}
};

/*

Select is a collection of API methods, event handlers, event emitters and
buttons (for the `Buttons` extension) for DataTables. It provides the following
features, with an overview of how they are implemented:

## Selection of rows, columns and cells. Whether an item is selected or not is
   stored in:

* rows: a `_select_selected` property which contains a boolean value of the
  DataTables' `aoData` object for each row
* columns: a `_select_selected` property which contains a boolean value of the
  DataTables' `aoColumns` object for each column
* cells: a `_selected_cells` property which contains an array of boolean values
  of the `aoData` object for each row. The array is the same length as the
  columns array, with each element of it representing a cell.

This method of using boolean flags allows Select to operate when nodes have not
been created for rows / cells (DataTables' defer rendering feature).

## API methods

A range of API methods are available for triggering selection and de-selection
of rows. Methods are also available to configure the selection events that can
be triggered by an end user (such as which items are to be selected). To a large
extent, these of API methods *is* Select. It is basically a collection of helper
functions that can be used to select items in a DataTable.

Configuration of select is held in the object `_select` which is attached to the
DataTables settings object on initialisation. Select being available on a table
is not optional when Select is loaded, but its default is for selection only to
be available via the API - so the end user wouldn't be able to select rows
without additional configuration.

The `_select` object contains the following properties:

```
{
	items:string     - Can be `rows`, `columns` or `cells`. Defines what item 
	                   will be selected if the user is allowed to activate row
	                   selection using the mouse.
	style:string     - Can be `none`, `single`, `multi` or `os`. Defines the
	                   interaction style when selecting items
	blurable:boolean - If row selection can be cleared by clicking outside of
	                   the table
	info:boolean     - If the selection summary should be shown in the table
	                   information elements
}
```

In addition to the API methods, Select also extends the DataTables selector
options for rows, columns and cells adding a `selected` option to the selector
options object, allowing the developer to select only selected items or
unselected items.

## Mouse selection of items

Clicking on items can be used to select items. This is done by a simple event
handler that will select the items using the API methods.

 */


/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Local functions
 */

/**
 * Add one or more cells to the selection when shift clicking in OS selection
 * style cell selection.
 *
 * Cell range is more complicated than row and column as we want to select
 * in the visible grid rather than by index in sequence. For example, if you
 * click first in cell 1-1 and then shift click in 2-2 - cells 1-2 and 2-1
 * should also be selected (and not 1-3, 1-4. etc)
 * 
 * @param  {DataTable.Api} dt   DataTable
 * @param  {object}        idx  Cell index to select to
 * @param  {object}        last Cell index to select from
 * @private
 */
function cellRange( dt, idx, last )
{
	var indexes;
	var columnIndexes;
	var rowIndexes;
	var selectColumns = function ( start, end ) {
		if ( start > end ) {
			var tmp = end;
			end = start;
			start = tmp;
		}
		
		var record = false;
		return dt.columns( ':visible' ).indexes().filter( function (i) {
			if ( i === start ) {
				record = true;
			}
			
			if ( i === end ) { // not else if, as start might === end
				record = false;
				return true;
			}

			return record;
		} );
	};

	var selectRows = function ( start, end ) {
		var indexes = dt.rows( { search: 'applied' } ).indexes();

		// Which comes first - might need to swap
		if ( indexes.indexOf( start ) > indexes.indexOf( end ) ) {
			var tmp = end;
			end = start;
			start = tmp;
		}

		var record = false;
		return indexes.filter( function (i) {
			if ( i === start ) {
				record = true;
			}
			
			if ( i === end ) {
				record = false;
				return true;
			}

			return record;
		} );
	};

	if ( ! dt.cells( { selected: true } ).any() && ! last ) {
		// select from the top left cell to this one
		columnIndexes = selectColumns( 0, idx.column );
		rowIndexes = selectRows( 0 , idx.row );
	}
	else {
		// Get column indexes between old and new
		columnIndexes = selectColumns( last.column, idx.column );
		rowIndexes = selectRows( last.row , idx.row );
	}

	indexes = dt.cells( rowIndexes, columnIndexes ).flatten();

	if ( ! dt.cells( idx, { selected: true } ).any() ) {
		// Select range
		dt.cells( indexes ).select();
	}
	else {
		// Deselect range
		dt.cells( indexes ).deselect();
	}
}

/**
 * Disable mouse selection by removing the selectors
 *
 * @param {DataTable.Api} dt DataTable to remove events from
 * @private
 */
function disableMouseSelection( dt )
{
	var ctx = dt.settings()[0];
	var selector = ctx._select.selector;

	$( dt.table().body() )
		.off( 'mousedown.dtSelect', selector )
		.off( 'mouseup.dtSelect', selector )
		.off( 'click.dtSelect', selector );

	$('body').off( 'click.dtSelect' );
}

/**
 * Attach mouse listeners to the table to allow mouse selection of items
 *
 * @param {DataTable.Api} dt DataTable to remove events from
 * @private
 */
function enableMouseSelection ( dt )
{
	var body = $( dt.table().body() );
	var ctx = dt.settings()[0];
	var selector = ctx._select.selector;

	body
		.on( 'mousedown.dtSelect', selector, function(e) {
			// Disallow text selection for shift clicking on the table so multi
			// element selection doesn't look terrible!
			if ( e.shiftKey || e.metaKey || e.ctrlKey ) {
				body
					.css( '-moz-user-select', 'none' )
					.one('selectstart.dtSelect', selector, function () {
						return false;
					} );
			}
		} )
		.on( 'mouseup.dtSelect', selector, function() {
			// Allow text selection to occur again, Mozilla style (tested in FF
			// 35.0.1 - still required)
			body.css( '-moz-user-select', '' );
		} )
		.on( 'click.dtSelect', selector, function ( e ) {
			var items = dt.select.items();
			var idx;

			// If text was selected (click and drag), then we shouldn't change
			// the row's selected state
			if ( window.getSelection && window.getSelection().toString() ) {
				return;
			}

			var ctx = dt.settings()[0];

			// Ignore clicks inside a sub-table
			if ( $(e.target).closest('div.dataTables_wrapper')[0] != dt.table().container() ) {
				return;
			}

			var cell = dt.cell( $(e.target).closest('td, th') );

			// Check the cell actually belongs to the host DataTable (so child
			// rows, etc, are ignored)
			if ( ! cell.any() ) {
				return;
			}

			var event = $.Event('user-select.dt');
			eventTrigger( dt, event, [ items, cell, e ] );

			if ( event.isDefaultPrevented() ) {
				return;
			}

			var cellIndex = cell.index();
			if ( items === 'row' ) {
				idx = cellIndex.row;
				typeSelect( e, dt, ctx, 'row', idx );
			}
			else if ( items === 'column' ) {
				idx = cell.index().column;
				typeSelect( e, dt, ctx, 'column', idx );
			}
			else if ( items === 'cell' ) {
				idx = cell.index();
				typeSelect( e, dt, ctx, 'cell', idx );
			}

			ctx._select_lastCell = cellIndex;
		} );

	// Blurable
	$('body').on( 'click.dtSelect', function ( e ) {
		if ( ctx._select.blurable ) {
			// If the click was inside the DataTables container, don't blur
			if ( $(e.target).parents().filter( dt.table().container() ).length ) {
				return;
			}

			// Don't blur in Editor form
			if ( $(e.target).parents('div.DTE').length ) {
				return;
			}

			clear( ctx, true );
		}
	} );
}

/**
 * Trigger an event on a DataTable
 *
 * @param {DataTable.Api} api      DataTable to trigger events on
 * @param  {boolean}      selected true if selected, false if deselected
 * @param  {string}       type     Item type acting on
 * @param  {boolean}      any      Require that there are values before
 *     triggering
 * @private
 */
function eventTrigger ( api, type, args, any )
{
	if ( any && ! api.flatten().length ) {
		return;
	}

	if ( typeof type === 'string' ) {
		type = type +'.dt';
	}

	args.unshift( api );

	$(api.table().node()).triggerHandler( type, args );
}

/**
 * Update the information element of the DataTable showing information about the
 * items selected. This is done by adding tags to the existing text
 * 
 * @param {DataTable.Api} api DataTable to update
 * @private
 */
function info ( api )
{
	var ctx = api.settings()[0];

	if ( ! ctx._select.info || ! ctx.aanFeatures.i ) {
		return;
	}

	var output  = $('<span class="select-info"/>');
	var add = function ( name, num ) {
		output.append( $('<span class="select-item"/>').append( api.i18n(
			'select.'+name+'s',
			{ _: '%d '+name+'s selected', 0: '', 1: '1 '+name+' selected' },
			num
		) ) );
	};

	add( 'row',    api.rows( { selected: true } ).flatten().length );
	add( 'column', api.columns( { selected: true } ).flatten().length );
	add( 'cell',   api.cells( { selected: true } ).flatten().length );

	// Internal knowledge of DataTables to loop over all information elements
	$.each( ctx.aanFeatures.i, function ( i, el ) {
		el = $(el);

		var exisiting = el.children('span.select-info');
		if ( exisiting.length ) {
			exisiting.remove();
		}

		if ( output.text() !== '' ) {
			el.append( output );
		}
	} );
}

/**
 * Initialisation of a new table. Attach event handlers and callbacks to allow
 * Select to operate correctly.
 *
 * This will occur _after_ the initial DataTables initialisation, although
 * before Ajax data is rendered, if there is ajax data
 *
 * @param  {DataTable.settings} ctx Settings object to operate on
 * @private
 */
function init ( ctx ) {
	var api = new DataTable.Api( ctx );

	// Row callback so that classes can be added to rows and cells if the item
	// was selected before the element was created. This will happen with the
	// `deferRender` option enabled.
	// 
	// This method of attaching to `aoRowCreatedCallback` is a hack until
	// DataTables has proper events for row manipulation If you are reviewing
	// this code to create your own plug-ins, please do not do this!
	ctx.aoRowCreatedCallback.push( {
		fn: function ( row, data, index ) {
			var i, ien;
			var d = ctx.aoData[ index ];

			// Row
			if ( d._select_selected ) {
				$( row ).addClass( ctx._select.className );
			}

			// Cells and columns - if separated out, we would need to do two
			// loops, so it makes sense to combine them into a single one
			for ( i=0, ien=ctx.aoColumns.length ; i<ien ; i++ ) {
				if ( ctx.aoColumns[i]._select_selected || (d._selected_cells && d._selected_cells[i]) ) {
					$(d.anCells[i]).addClass( ctx._select.className );
				}
			}
		},
		sName: 'select-deferRender'
	} );

	// On Ajax reload we want to reselect all rows which are currently selected,
	// if there is an rowId (i.e. a unique value to identify each row with)
	api.on( 'preXhr.dt.dtSelect', function () {
		// note that column selection doesn't need to be cached and then
		// reselected, as they are already selected
		var rows = api.rows( { selected: true } ).ids( true ).filter( function ( d ) {
			return d !== undefined;
		} );

		var cells = api.cells( { selected: true } ).eq(0).map( function ( cellIdx ) {
			var id = api.row( cellIdx.row ).id( true );
			return id ?
				{ row: id, column: cellIdx.column } :
				undefined;
		} ).filter( function ( d ) {
			return d !== undefined;
		} );

		// On the next draw, reselect the currently selected items
		api.one( 'draw.dt.dtSelect', function () {
			api.rows( rows ).select();

			// `cells` is not a cell index selector, so it needs a loop
			if ( cells.any() ) {
				cells.each( function ( id ) {
					api.cells( id.row, id.column ).select();
				} );
			}
		} );
	} );

	// Update the table information element with selected item summary
	api.on( 'draw.dtSelect.dt select.dtSelect.dt deselect.dtSelect.dt info.dt', function () {
		info( api );
	} );

	// Clean up and release
	api.on( 'destroy.dtSelect', function () {
		disableMouseSelection( api );
		api.off( '.dtSelect' );
	} );
}

/**
 * Add one or more items (rows or columns) to the selection when shift clicking
 * in OS selection style
 *
 * @param  {DataTable.Api} dt   DataTable
 * @param  {string}        type Row or column range selector
 * @param  {object}        idx  Item index to select to
 * @param  {object}        last Item index to select from
 * @private
 */
function rowColumnRange( dt, type, idx, last )
{
	// Add a range of rows from the last selected row to this one
	var indexes = dt[type+'s']( { search: 'applied' } ).indexes();
	var idx1 = $.inArray( last, indexes );
	var idx2 = $.inArray( idx, indexes );

	if ( ! dt[type+'s']( { selected: true } ).any() && idx1 === -1 ) {
		// select from top to here - slightly odd, but both Windows and Mac OS
		// do this
		indexes.splice( $.inArray( idx, indexes )+1, indexes.length );
	}
	else {
		// reverse so we can shift click 'up' as well as down
		if ( idx1 > idx2 ) {
			var tmp = idx2;
			idx2 = idx1;
			idx1 = tmp;
		}

		indexes.splice( idx2+1, indexes.length );
		indexes.splice( 0, idx1 );
	}

	if ( ! dt[type]( idx, { selected: true } ).any() ) {
		// Select range
		dt[type+'s']( indexes ).select();
	}
	else {
		// Deselect range - need to keep the clicked on row selected
		indexes.splice( $.inArray( idx, indexes ), 1 );
		dt[type+'s']( indexes ).deselect();
	}
}

/**
 * Clear all selected items
 *
 * @param  {DataTable.settings} ctx Settings object of the host DataTable
 * @param  {boolean} [force=false] Force the de-selection to happen, regardless
 *     of selection style
 * @private
 */
function clear( ctx, force )
{
	if ( force || ctx._select.style === 'single' ) {
		var api = new DataTable.Api( ctx );
		
		api.rows( { selected: true } ).deselect();
		api.columns( { selected: true } ).deselect();
		api.cells( { selected: true } ).deselect();
	}
}

/**
 * Select items based on the current configuration for style and items.
 *
 * @param  {object}             e    Mouse event object
 * @param  {DataTables.Api}     dt   DataTable
 * @param  {DataTable.settings} ctx  Settings object of the host DataTable
 * @param  {string}             type Items to select
 * @param  {int|object}         idx  Index of the item to select
 * @private
 */
function typeSelect ( e, dt, ctx, type, idx )
{
	var style = dt.select.style();
	var isSelected = dt[type]( idx, { selected: true } ).any();

	if ( style === 'os' ) {
		if ( e.ctrlKey || e.metaKey ) {
			// Add or remove from the selection
			dt[type]( idx ).select( ! isSelected );
		}
		else if ( e.shiftKey ) {
			if ( type === 'cell' ) {
				cellRange( dt, idx, ctx._select_lastCell || null );
			}
			else {
				rowColumnRange( dt, type, idx, ctx._select_lastCell ?
					ctx._select_lastCell[type] :
					null
				);
			}
		}
		else {
			// No cmd or shift click - deselect if selected, or select
			// this row only
			var selected = dt[type+'s']( { selected: true } );

			if ( isSelected && selected.flatten().length === 1 ) {
				dt[type]( idx ).deselect();
			}
			else {
				selected.deselect();
				dt[type]( idx ).select();
			}
		}
	} else if ( style == 'multi+shift' ) {
		if ( e.shiftKey ) {
			if ( type === 'cell' ) {
				cellRange( dt, idx, ctx._select_lastCell || null );
			}
			else {
				rowColumnRange( dt, type, idx, ctx._select_lastCell ?
					ctx._select_lastCell[type] :
					null
				);
			}
		}
		else {
			dt[ type ]( idx ).select( ! isSelected );
		}
	}
	else {
		dt[ type ]( idx ).select( ! isSelected );
	}
}



/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * DataTables selectors
 */

// row and column are basically identical just assigned to different properties
// and checking a different array, so we can dynamically create the functions to
// reduce the code size
$.each( [
	{ type: 'row', prop: 'aoData' },
	{ type: 'column', prop: 'aoColumns' }
], function ( i, o ) {
	DataTable.ext.selector[ o.type ].push( function ( settings, opts, indexes ) {
		var selected = opts.selected;
		var data;
		var out = [];

		if ( selected === undefined ) {
			return indexes;
		}

		for ( var i=0, ien=indexes.length ; i<ien ; i++ ) {
			data = settings[ o.prop ][ indexes[i] ];

			if ( (selected === true && data._select_selected === true) ||
			     (selected === false && ! data._select_selected )
			) {
				out.push( indexes[i] );
			}
		}

		return out;
	} );
} );

DataTable.ext.selector.cell.push( function ( settings, opts, cells ) {
	var selected = opts.selected;
	var rowData;
	var out = [];

	if ( selected === undefined ) {
		return cells;
	}

	for ( var i=0, ien=cells.length ; i<ien ; i++ ) {
		rowData = settings.aoData[ cells[i].row ];

		if ( (selected === true && rowData._selected_cells && rowData._selected_cells[ cells[i].column ] === true) ||
		     (selected === false && ( ! rowData._selected_cells || ! rowData._selected_cells[ cells[i].column ] ) )
		) {
			out.push( cells[i] );
		}
	}

	return out;
} );



/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * DataTables API
 *
 * For complete documentation, please refer to the docs/api directory or the
 * DataTables site
 */

// Local variables to improve compression
var apiRegister = DataTable.Api.register;
var apiRegisterPlural = DataTable.Api.registerPlural;

apiRegister( 'select()', function () {
	return this.iterator( 'table', function ( ctx ) {
		DataTable.select.init( new DataTable.Api( ctx ) );
	} );
} );

apiRegister( 'select.blurable()', function ( flag ) {
	if ( flag === undefined ) {
		return this.context[0]._select.blurable;
	}

	return this.iterator( 'table', function ( ctx ) {
		ctx._select.blurable = flag;
	} );
} );

apiRegister( 'select.info()', function ( flag ) {
	if ( info === undefined ) {
		return this.context[0]._select.info;
	}

	return this.iterator( 'table', function ( ctx ) {
		ctx._select.info = flag;
	} );
} );

apiRegister( 'select.items()', function ( items ) {
	if ( items === undefined ) {
		return this.context[0]._select.items;
	}

	return this.iterator( 'table', function ( ctx ) {
		ctx._select.items = items;

		eventTrigger( new DataTable.Api( ctx ), 'selectItems', [ items ] );
	} );
} );

// Takes effect from the _next_ selection. None disables future selection, but
// does not clear the current selection. Use the `deselect` methods for that
apiRegister( 'select.style()', function ( style ) {
	if ( style === undefined ) {
		return this.context[0]._select.style;
	}

	return this.iterator( 'table', function ( ctx ) {
		ctx._select.style = style;

		if ( ! ctx._select_init ) {
			init( ctx );
		}

		// Add / remove mouse event handlers. They aren't required when only
		// API selection is available
		var dt = new DataTable.Api( ctx );
		disableMouseSelection( dt );
		
		if ( style !== 'api' ) {
			enableMouseSelection( dt );
		}

		eventTrigger( new DataTable.Api( ctx ), 'selectStyle', [ style ] );
	} );
} );

apiRegister( 'select.selector()', function ( selector ) {
	if ( selector === undefined ) {
		return this.context[0]._select.selector;
	}

	return this.iterator( 'table', function ( ctx ) {
		disableMouseSelection( new DataTable.Api( ctx ) );

		ctx._select.selector = selector;

		if ( ctx._select.style !== 'api' ) {
			enableMouseSelection( new DataTable.Api( ctx ) );
		}
	} );
} );



apiRegisterPlural( 'rows().select()', 'row().select()', function ( select ) {
	var api = this;

	if ( select === false ) {
		return this.deselect();
	}

	this.iterator( 'row', function ( ctx, idx ) {
		clear( ctx );

		ctx.aoData[ idx ]._select_selected = true;
		$( ctx.aoData[ idx ].nTr ).addClass( ctx._select.className );
	} );

	this.iterator( 'table', function ( ctx, i ) {
		eventTrigger( api, 'select', [ 'row', api[i] ], true );
	} );

	return this;
} );

apiRegisterPlural( 'columns().select()', 'column().select()', function ( select ) {
	var api = this;

	if ( select === false ) {
		return this.deselect();
	}

	this.iterator( 'column', function ( ctx, idx ) {
		clear( ctx );

		ctx.aoColumns[ idx ]._select_selected = true;

		var column = new DataTable.Api( ctx ).column( idx );

		$( column.header() ).addClass( ctx._select.className );
		$( column.footer() ).addClass( ctx._select.className );

		column.nodes().to$().addClass( ctx._select.className );
	} );

	this.iterator( 'table', function ( ctx, i ) {
		eventTrigger( api, 'select', [ 'column', api[i] ], true );
	} );

	return this;
} );

apiRegisterPlural( 'cells().select()', 'cell().select()', function ( select ) {
	var api = this;

	if ( select === false ) {
		return this.deselect();
	}

	this.iterator( 'cell', function ( ctx, rowIdx, colIdx ) {
		clear( ctx );

		var data = ctx.aoData[ rowIdx ];

		if ( data._selected_cells === undefined ) {
			data._selected_cells = [];
		}

		data._selected_cells[ colIdx ] = true;

		if ( data.anCells ) {
			$( data.anCells[ colIdx ] ).addClass( ctx._select.className );
		}
	} );

	this.iterator( 'table', function ( ctx, i ) {
		eventTrigger( api, 'select', [ 'cell', api[i] ], true );
	} );

	return this;
} );


apiRegisterPlural( 'rows().deselect()', 'row().deselect()', function () {
	var api = this;

	this.iterator( 'row', function ( ctx, idx ) {
		ctx.aoData[ idx ]._select_selected = false;
		$( ctx.aoData[ idx ].nTr ).removeClass( ctx._select.className );
	} );

	this.iterator( 'table', function ( ctx, i ) {
		eventTrigger( api, 'deselect', [ 'row', api[i] ], true );
	} );

	return this;
} );

apiRegisterPlural( 'columns().deselect()', 'column().deselect()', function () {
	var api = this;

	this.iterator( 'column', function ( ctx, idx ) {
		ctx.aoColumns[ idx ]._select_selected = false;

		var api = new DataTable.Api( ctx );
		var column = api.column( idx );

		$( column.header() ).removeClass( ctx._select.className );
		$( column.footer() ).removeClass( ctx._select.className );

		// Need to loop over each cell, rather than just using
		// `column().nodes()` as cells which are individually selected should
		// not have the `selected` class removed from them
		api.cells( null, idx ).indexes().each( function (cellIdx) {
			var data = ctx.aoData[ cellIdx.row ];
			var cellSelected = data._selected_cells;

			if ( data.anCells && (! cellSelected || ! cellSelected[ cellIdx.column ]) ) {
				$( data.anCells[ cellIdx.column  ] ).removeClass( ctx._select.className );
			}
		} );
	} );

	this.iterator( 'table', function ( ctx, i ) {
		eventTrigger( api, 'deselect', [ 'column', api[i] ], true );
	} );

	return this;
} );

apiRegisterPlural( 'cells().deselect()', 'cell().deselect()', function () {
	var api = this;

	this.iterator( 'cell', function ( ctx, rowIdx, colIdx ) {
		var data = ctx.aoData[ rowIdx ];

		data._selected_cells[ colIdx ] = false;

		// Remove class only if the cells exist, and the cell is not column
		// selected, in which case the class should remain (since it is selected
		// in the column)
		if ( data.anCells && ! ctx.aoColumns[ colIdx ]._select_selected ) {
			$( data.anCells[ colIdx ] ).removeClass( ctx._select.className );
		}
	} );

	this.iterator( 'table', function ( ctx, i ) {
		eventTrigger( api, 'deselect', [ 'cell', api[i] ], true );
	} );

	return this;
} );



/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Buttons
 */
function i18n( label, def ) {
	return function (dt) {
		return dt.i18n( 'buttons.'+label, def );
	};
}

$.extend( DataTable.ext.buttons, {
	selected: {
		text: i18n( 'selected', 'Selected' ),
		className: 'buttons-selected',
		init: function ( dt ) {
			var that = this;

			// .DT namespace listeners are removed by DataTables automatically
			// on table destroy
			dt.on( 'draw.dt.DT select.dt.DT deselect.dt.DT', function () {
				var enable = that.rows( { selected: true } ).any() ||
				             that.columns( { selected: true } ).any() ||
				             that.cells( { selected: true } ).any();

				that.enable( enable );
			} );

			this.disable();
		}
	},
	selectedSingle: {
		text: i18n( 'selectedSingle', 'Selected single' ),
		className: 'buttons-selected-single',
		init: function ( dt ) {
			var that = this;

			dt.on( 'draw.dt.DT select.dt.DT deselect.dt.DT', function () {
				var count = dt.rows( { selected: true } ).flatten().length +
				            dt.columns( { selected: true } ).flatten().length +
				            dt.cells( { selected: true } ).flatten().length;

				that.enable( count === 1 );
			} );

			this.disable();
		}
	},
	selectAll: {
		text: i18n( 'selectAll', 'Select all' ),
		className: 'buttons-select-all',
		action: function () {
			var items = this.select.items();
			this[ items+'s' ]().select();
		}
	},
	selectNone: {
		text: i18n( 'selectNone', 'Deselect all' ),
		className: 'buttons-select-none',
		action: function () {
			clear( this.settings()[0], true );
		},
		init: function ( dt ) {
			var that = this;

			dt.on( 'draw.dt.DT select.dt.DT deselect.dt.DT', function () {
				var count = dt.rows( { selected: true } ).flatten().length +
				            dt.columns( { selected: true } ).flatten().length +
				            dt.cells( { selected: true } ).flatten().length;

				that.enable( count > 0 );
			} );

			this.disable();
		}
	}
} );

$.each( [ 'Row', 'Column', 'Cell' ], function ( i, item ) {
	var lc = item.toLowerCase();

	DataTable.ext.buttons[ 'select'+item+'s' ] = {
		text: i18n( 'select'+item+'s', 'Select '+lc+'s' ),
		className: 'buttons-select-'+lc+'s',
		action: function () {
			this.select.items( lc );
		},
		init: function ( dt ) {
			var that = this;

			dt.on( 'selectItems.dt.DT', function ( e, ctx, items ) {
				that.active( items === lc );
			} );
		}
	};
} );



/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Initialisation
 */

// DataTables creation - check if select has been defined in the options. Note
// this required that the table be in the document! If it isn't then something
// needs to trigger this method unfortunately. The next major release of
// DataTables will rework the events and address this.
$(document).on( 'preInit.dt.dtSelect', function (e, ctx) {
	if ( e.namespace !== 'dt' ) {
		return;
	}

	DataTable.select.init( new DataTable.Api( ctx ) );
} );


return DataTable.select;
}));


;
/*!
 DataTables 1.10.12
 ©2008-2015 SpryMedia Ltd - datatables.net/license
*/
(function(h){"function"===typeof define&&define.amd?define(["jquery"],function(D){return h(D,window,document)}):"object"===typeof exports?module.exports=function(D,I){D||(D=window);I||(I="undefined"!==typeof window?require("jquery"):require("jquery")(D));return h(I,D,D.document)}:h(jQuery,window,document)})(function(h,D,I,k){function X(a){var b,c,d={};h.each(a,function(e){if((b=e.match(/^([^A-Z]+?)([A-Z])/))&&-1!=="a aa ai ao as b fn i m o s ".indexOf(b[1]+" "))c=e.replace(b[0],b[2].toLowerCase()),
d[c]=e,"o"===b[1]&&X(a[e])});a._hungarianMap=d}function K(a,b,c){a._hungarianMap||X(a);var d;h.each(b,function(e){d=a._hungarianMap[e];if(d!==k&&(c||b[d]===k))"o"===d.charAt(0)?(b[d]||(b[d]={}),h.extend(!0,b[d],b[e]),K(a[d],b[d],c)):b[d]=b[e]})}function Da(a){var b=m.defaults.oLanguage,c=a.sZeroRecords;!a.sEmptyTable&&(c&&"No data available in table"===b.sEmptyTable)&&E(a,a,"sZeroRecords","sEmptyTable");!a.sLoadingRecords&&(c&&"Loading..."===b.sLoadingRecords)&&E(a,a,"sZeroRecords","sLoadingRecords");
a.sInfoThousands&&(a.sThousands=a.sInfoThousands);(a=a.sDecimal)&&db(a)}function eb(a){A(a,"ordering","bSort");A(a,"orderMulti","bSortMulti");A(a,"orderClasses","bSortClasses");A(a,"orderCellsTop","bSortCellsTop");A(a,"order","aaSorting");A(a,"orderFixed","aaSortingFixed");A(a,"paging","bPaginate");A(a,"pagingType","sPaginationType");A(a,"pageLength","iDisplayLength");A(a,"searching","bFilter");"boolean"===typeof a.sScrollX&&(a.sScrollX=a.sScrollX?"100%":"");"boolean"===typeof a.scrollX&&(a.scrollX=
a.scrollX?"100%":"");if(a=a.aoSearchCols)for(var b=0,c=a.length;b<c;b++)a[b]&&K(m.models.oSearch,a[b])}function fb(a){A(a,"orderable","bSortable");A(a,"orderData","aDataSort");A(a,"orderSequence","asSorting");A(a,"orderDataType","sortDataType");var b=a.aDataSort;b&&!h.isArray(b)&&(a.aDataSort=[b])}function gb(a){if(!m.__browser){var b={};m.__browser=b;var c=h("<div/>").css({position:"fixed",top:0,left:0,height:1,width:1,overflow:"hidden"}).append(h("<div/>").css({position:"absolute",top:1,left:1,
width:100,overflow:"scroll"}).append(h("<div/>").css({width:"100%",height:10}))).appendTo("body"),d=c.children(),e=d.children();b.barWidth=d[0].offsetWidth-d[0].clientWidth;b.bScrollOversize=100===e[0].offsetWidth&&100!==d[0].clientWidth;b.bScrollbarLeft=1!==Math.round(e.offset().left);b.bBounding=c[0].getBoundingClientRect().width?!0:!1;c.remove()}h.extend(a.oBrowser,m.__browser);a.oScroll.iBarWidth=m.__browser.barWidth}function hb(a,b,c,d,e,f){var g,j=!1;c!==k&&(g=c,j=!0);for(;d!==e;)a.hasOwnProperty(d)&&
(g=j?b(g,a[d],d,a):a[d],j=!0,d+=f);return g}function Ea(a,b){var c=m.defaults.column,d=a.aoColumns.length,c=h.extend({},m.models.oColumn,c,{nTh:b?b:I.createElement("th"),sTitle:c.sTitle?c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[d],mData:c.mData?c.mData:d,idx:d});a.aoColumns.push(c);c=a.aoPreSearchCols;c[d]=h.extend({},m.models.oSearch,c[d]);ja(a,d,h(b).data())}function ja(a,b,c){var b=a.aoColumns[b],d=a.oClasses,e=h(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=e.attr("width")||null;var f=
(e.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);f&&(b.sWidthOrig=f[1])}c!==k&&null!==c&&(fb(c),K(m.defaults.column,c),c.mDataProp!==k&&!c.mData&&(c.mData=c.mDataProp),c.sType&&(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),h.extend(b,c),E(b,c,"sWidth","sWidthOrig"),c.iDataSort!==k&&(b.aDataSort=[c.iDataSort]),E(b,c,"aDataSort"));var g=b.mData,j=Q(g),i=b.mRender?Q(b.mRender):null,c=function(a){return"string"===typeof a&&-1!==a.indexOf("@")};b._bAttrSrc=h.isPlainObject(g)&&
(c(g.sort)||c(g.type)||c(g.filter));b._setter=null;b.fnGetData=function(a,b,c){var d=j(a,b,k,c);return i&&b?i(d,b,a,c):d};b.fnSetData=function(a,b,c){return R(g)(a,b,c)};"number"!==typeof g&&(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,e.addClass(d.sSortableNone));a=-1!==h.inArray("asc",b.asSorting);c=-1!==h.inArray("desc",b.asSorting);!b.bSortable||!a&&!c?(b.sSortingClass=d.sSortableNone,b.sSortingClassJUI=""):a&&!c?(b.sSortingClass=d.sSortableAsc,b.sSortingClassJUI=d.sSortJUIAscAllowed):
!a&&c?(b.sSortingClass=d.sSortableDesc,b.sSortingClassJUI=d.sSortJUIDescAllowed):(b.sSortingClass=d.sSortable,b.sSortingClassJUI=d.sSortJUI)}function Y(a){if(!1!==a.oFeatures.bAutoWidth){var b=a.aoColumns;Fa(a);for(var c=0,d=b.length;c<d;c++)b[c].nTh.style.width=b[c].sWidth}b=a.oScroll;(""!==b.sY||""!==b.sX)&&ka(a);u(a,null,"column-sizing",[a])}function Z(a,b){var c=la(a,"bVisible");return"number"===typeof c[b]?c[b]:null}function $(a,b){var c=la(a,"bVisible"),c=h.inArray(b,c);return-1!==c?c:null}
function aa(a){var b=0;h.each(a.aoColumns,function(a,d){d.bVisible&&"none"!==h(d.nTh).css("display")&&b++});return b}function la(a,b){var c=[];h.map(a.aoColumns,function(a,e){a[b]&&c.push(e)});return c}function Ga(a){var b=a.aoColumns,c=a.aoData,d=m.ext.type.detect,e,f,g,j,i,h,l,q,t;e=0;for(f=b.length;e<f;e++)if(l=b[e],t=[],!l.sType&&l._sManualType)l.sType=l._sManualType;else if(!l.sType){g=0;for(j=d.length;g<j;g++){i=0;for(h=c.length;i<h;i++){t[i]===k&&(t[i]=B(a,i,e,"type"));q=d[g](t[i],a);if(!q&&
g!==d.length-1)break;if("html"===q)break}if(q){l.sType=q;break}}l.sType||(l.sType="string")}}function ib(a,b,c,d){var e,f,g,j,i,n,l=a.aoColumns;if(b)for(e=b.length-1;0<=e;e--){n=b[e];var q=n.targets!==k?n.targets:n.aTargets;h.isArray(q)||(q=[q]);f=0;for(g=q.length;f<g;f++)if("number"===typeof q[f]&&0<=q[f]){for(;l.length<=q[f];)Ea(a);d(q[f],n)}else if("number"===typeof q[f]&&0>q[f])d(l.length+q[f],n);else if("string"===typeof q[f]){j=0;for(i=l.length;j<i;j++)("_all"==q[f]||h(l[j].nTh).hasClass(q[f]))&&
d(j,n)}}if(c){e=0;for(a=c.length;e<a;e++)d(e,c[e])}}function N(a,b,c,d){var e=a.aoData.length,f=h.extend(!0,{},m.models.oRow,{src:c?"dom":"data",idx:e});f._aData=b;a.aoData.push(f);for(var g=a.aoColumns,j=0,i=g.length;j<i;j++)g[j].sType=null;a.aiDisplayMaster.push(e);b=a.rowIdFn(b);b!==k&&(a.aIds[b]=f);(c||!a.oFeatures.bDeferRender)&&Ha(a,e,c,d);return e}function ma(a,b){var c;b instanceof h||(b=h(b));return b.map(function(b,e){c=Ia(a,e);return N(a,c.data,e,c.cells)})}function B(a,b,c,d){var e=a.iDraw,
f=a.aoColumns[c],g=a.aoData[b]._aData,j=f.sDefaultContent,i=f.fnGetData(g,d,{settings:a,row:b,col:c});if(i===k)return a.iDrawError!=e&&null===j&&(L(a,0,"Requested unknown parameter "+("function"==typeof f.mData?"{function}":"'"+f.mData+"'")+" for row "+b+", column "+c,4),a.iDrawError=e),j;if((i===g||null===i)&&null!==j&&d!==k)i=j;else if("function"===typeof i)return i.call(g);return null===i&&"display"==d?"":i}function jb(a,b,c,d){a.aoColumns[c].fnSetData(a.aoData[b]._aData,d,{settings:a,row:b,col:c})}
function Ja(a){return h.map(a.match(/(\\.|[^\.])+/g)||[""],function(a){return a.replace(/\\./g,".")})}function Q(a){if(h.isPlainObject(a)){var b={};h.each(a,function(a,c){c&&(b[a]=Q(c))});return function(a,c,f,g){var j=b[c]||b._;return j!==k?j(a,c,f,g):a}}if(null===a)return function(a){return a};if("function"===typeof a)return function(b,c,f,g){return a(b,c,f,g)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var c=function(a,b,f){var g,j;if(""!==f){j=Ja(f);
for(var i=0,n=j.length;i<n;i++){f=j[i].match(ba);g=j[i].match(U);if(f){j[i]=j[i].replace(ba,"");""!==j[i]&&(a=a[j[i]]);g=[];j.splice(0,i+1);j=j.join(".");if(h.isArray(a)){i=0;for(n=a.length;i<n;i++)g.push(c(a[i],b,j))}a=f[0].substring(1,f[0].length-1);a=""===a?g:g.join(a);break}else if(g){j[i]=j[i].replace(U,"");a=a[j[i]]();continue}if(null===a||a[j[i]]===k)return k;a=a[j[i]]}}return a};return function(b,e){return c(b,e,a)}}return function(b){return b[a]}}function R(a){if(h.isPlainObject(a))return R(a._);
if(null===a)return function(){};if("function"===typeof a)return function(b,d,e){a(b,"set",d,e)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var b=function(a,d,e){var e=Ja(e),f;f=e[e.length-1];for(var g,j,i=0,n=e.length-1;i<n;i++){g=e[i].match(ba);j=e[i].match(U);if(g){e[i]=e[i].replace(ba,"");a[e[i]]=[];f=e.slice();f.splice(0,i+1);g=f.join(".");if(h.isArray(d)){j=0;for(n=d.length;j<n;j++)f={},b(f,d[j],g),a[e[i]].push(f)}else a[e[i]]=d;return}j&&(e[i]=e[i].replace(U,
""),a=a[e[i]](d));if(null===a[e[i]]||a[e[i]]===k)a[e[i]]={};a=a[e[i]]}if(f.match(U))a[f.replace(U,"")](d);else a[f.replace(ba,"")]=d};return function(c,d){return b(c,d,a)}}return function(b,d){b[a]=d}}function Ka(a){return G(a.aoData,"_aData")}function na(a){a.aoData.length=0;a.aiDisplayMaster.length=0;a.aiDisplay.length=0;a.aIds={}}function oa(a,b,c){for(var d=-1,e=0,f=a.length;e<f;e++)a[e]==b?d=e:a[e]>b&&a[e]--; -1!=d&&c===k&&a.splice(d,1)}function ca(a,b,c,d){var e=a.aoData[b],f,g=function(c,d){for(;c.childNodes.length;)c.removeChild(c.firstChild);
c.innerHTML=B(a,b,d,"display")};if("dom"===c||(!c||"auto"===c)&&"dom"===e.src)e._aData=Ia(a,e,d,d===k?k:e._aData).data;else{var j=e.anCells;if(j)if(d!==k)g(j[d],d);else{c=0;for(f=j.length;c<f;c++)g(j[c],c)}}e._aSortData=null;e._aFilterData=null;g=a.aoColumns;if(d!==k)g[d].sType=null;else{c=0;for(f=g.length;c<f;c++)g[c].sType=null;La(a,e)}}function Ia(a,b,c,d){var e=[],f=b.firstChild,g,j,i=0,n,l=a.aoColumns,q=a._rowReadObject,d=d!==k?d:q?{}:[],t=function(a,b){if("string"===typeof a){var c=a.indexOf("@");
-1!==c&&(c=a.substring(c+1),R(a)(d,b.getAttribute(c)))}},S=function(a){if(c===k||c===i)j=l[i],n=h.trim(a.innerHTML),j&&j._bAttrSrc?(R(j.mData._)(d,n),t(j.mData.sort,a),t(j.mData.type,a),t(j.mData.filter,a)):q?(j._setter||(j._setter=R(j.mData)),j._setter(d,n)):d[i]=n;i++};if(f)for(;f;){g=f.nodeName.toUpperCase();if("TD"==g||"TH"==g)S(f),e.push(f);f=f.nextSibling}else{e=b.anCells;f=0;for(g=e.length;f<g;f++)S(e[f])}if(b=b.firstChild?b:b.nTr)(b=b.getAttribute("id"))&&R(a.rowId)(d,b);return{data:d,cells:e}}
function Ha(a,b,c,d){var e=a.aoData[b],f=e._aData,g=[],j,i,n,l,q;if(null===e.nTr){j=c||I.createElement("tr");e.nTr=j;e.anCells=g;j._DT_RowIndex=b;La(a,e);l=0;for(q=a.aoColumns.length;l<q;l++){n=a.aoColumns[l];i=c?d[l]:I.createElement(n.sCellType);i._DT_CellIndex={row:b,column:l};g.push(i);if((!c||n.mRender||n.mData!==l)&&(!h.isPlainObject(n.mData)||n.mData._!==l+".display"))i.innerHTML=B(a,b,l,"display");n.sClass&&(i.className+=" "+n.sClass);n.bVisible&&!c?j.appendChild(i):!n.bVisible&&c&&i.parentNode.removeChild(i);
n.fnCreatedCell&&n.fnCreatedCell.call(a.oInstance,i,B(a,b,l),f,b,l)}u(a,"aoRowCreatedCallback",null,[j,f,b])}e.nTr.setAttribute("role","row")}function La(a,b){var c=b.nTr,d=b._aData;if(c){var e=a.rowIdFn(d);e&&(c.id=e);d.DT_RowClass&&(e=d.DT_RowClass.split(" "),b.__rowc=b.__rowc?pa(b.__rowc.concat(e)):e,h(c).removeClass(b.__rowc.join(" ")).addClass(d.DT_RowClass));d.DT_RowAttr&&h(c).attr(d.DT_RowAttr);d.DT_RowData&&h(c).data(d.DT_RowData)}}function kb(a){var b,c,d,e,f,g=a.nTHead,j=a.nTFoot,i=0===
h("th, td",g).length,n=a.oClasses,l=a.aoColumns;i&&(e=h("<tr/>").appendTo(g));b=0;for(c=l.length;b<c;b++)f=l[b],d=h(f.nTh).addClass(f.sClass),i&&d.appendTo(e),a.oFeatures.bSort&&(d.addClass(f.sSortingClass),!1!==f.bSortable&&(d.attr("tabindex",a.iTabIndex).attr("aria-controls",a.sTableId),Ma(a,f.nTh,b))),f.sTitle!=d[0].innerHTML&&d.html(f.sTitle),Na(a,"header")(a,d,f,n);i&&da(a.aoHeader,g);h(g).find(">tr").attr("role","row");h(g).find(">tr>th, >tr>td").addClass(n.sHeaderTH);h(j).find(">tr>th, >tr>td").addClass(n.sFooterTH);
if(null!==j){a=a.aoFooter[0];b=0;for(c=a.length;b<c;b++)f=l[b],f.nTf=a[b].cell,f.sClass&&h(f.nTf).addClass(f.sClass)}}function ea(a,b,c){var d,e,f,g=[],j=[],i=a.aoColumns.length,n;if(b){c===k&&(c=!1);d=0;for(e=b.length;d<e;d++){g[d]=b[d].slice();g[d].nTr=b[d].nTr;for(f=i-1;0<=f;f--)!a.aoColumns[f].bVisible&&!c&&g[d].splice(f,1);j.push([])}d=0;for(e=g.length;d<e;d++){if(a=g[d].nTr)for(;f=a.firstChild;)a.removeChild(f);f=0;for(b=g[d].length;f<b;f++)if(n=i=1,j[d][f]===k){a.appendChild(g[d][f].cell);
for(j[d][f]=1;g[d+i]!==k&&g[d][f].cell==g[d+i][f].cell;)j[d+i][f]=1,i++;for(;g[d][f+n]!==k&&g[d][f].cell==g[d][f+n].cell;){for(c=0;c<i;c++)j[d+c][f+n]=1;n++}h(g[d][f].cell).attr("rowspan",i).attr("colspan",n)}}}}function O(a){var b=u(a,"aoPreDrawCallback","preDraw",[a]);if(-1!==h.inArray(!1,b))C(a,!1);else{var b=[],c=0,d=a.asStripeClasses,e=d.length,f=a.oLanguage,g=a.iInitDisplayStart,j="ssp"==y(a),i=a.aiDisplay;a.bDrawing=!0;g!==k&&-1!==g&&(a._iDisplayStart=j?g:g>=a.fnRecordsDisplay()?0:g,a.iInitDisplayStart=
-1);var g=a._iDisplayStart,n=a.fnDisplayEnd();if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,C(a,!1);else if(j){if(!a.bDestroying&&!lb(a))return}else a.iDraw++;if(0!==i.length){f=j?a.aoData.length:n;for(j=j?0:g;j<f;j++){var l=i[j],q=a.aoData[l];null===q.nTr&&Ha(a,l);l=q.nTr;if(0!==e){var t=d[c%e];q._sRowStripe!=t&&(h(l).removeClass(q._sRowStripe).addClass(t),q._sRowStripe=t)}u(a,"aoRowCallback",null,[l,q._aData,c,j]);b.push(l);c++}}else c=f.sZeroRecords,1==a.iDraw&&"ajax"==y(a)?c=f.sLoadingRecords:
f.sEmptyTable&&0===a.fnRecordsTotal()&&(c=f.sEmptyTable),b[0]=h("<tr/>",{"class":e?d[0]:""}).append(h("<td />",{valign:"top",colSpan:aa(a),"class":a.oClasses.sRowEmpty}).html(c))[0];u(a,"aoHeaderCallback","header",[h(a.nTHead).children("tr")[0],Ka(a),g,n,i]);u(a,"aoFooterCallback","footer",[h(a.nTFoot).children("tr")[0],Ka(a),g,n,i]);d=h(a.nTBody);d.children().detach();d.append(h(b));u(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=!1}}function T(a,b){var c=a.oFeatures,d=c.bFilter;
c.bSort&&mb(a);d?fa(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;O(a);a._drawHold=!1}function nb(a){var b=a.oClasses,c=h(a.nTable),c=h("<div/>").insertBefore(c),d=a.oFeatures,e=h("<div/>",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=e[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var f=a.sDom.split(""),g,j,i,n,l,q,t=0;t<f.length;t++){g=null;j=f[t];if("<"==j){i=h("<div/>")[0];
n=f[t+1];if("'"==n||'"'==n){l="";for(q=2;f[t+q]!=n;)l+=f[t+q],q++;"H"==l?l=b.sJUIHeader:"F"==l&&(l=b.sJUIFooter);-1!=l.indexOf(".")?(n=l.split("."),i.id=n[0].substr(1,n[0].length-1),i.className=n[1]):"#"==l.charAt(0)?i.id=l.substr(1,l.length-1):i.className=l;t+=q}e.append(i);e=h(i)}else if(">"==j)e=e.parent();else if("l"==j&&d.bPaginate&&d.bLengthChange)g=ob(a);else if("f"==j&&d.bFilter)g=pb(a);else if("r"==j&&d.bProcessing)g=qb(a);else if("t"==j)g=rb(a);else if("i"==j&&d.bInfo)g=sb(a);else if("p"==
j&&d.bPaginate)g=tb(a);else if(0!==m.ext.feature.length){i=m.ext.feature;q=0;for(n=i.length;q<n;q++)if(j==i[q].cFeature){g=i[q].fnInit(a);break}}g&&(i=a.aanFeatures,i[j]||(i[j]=[]),i[j].push(g),e.append(g))}c.replaceWith(e);a.nHolding=null}function da(a,b){var c=h(b).children("tr"),d,e,f,g,j,i,n,l,q,t;a.splice(0,a.length);f=0;for(i=c.length;f<i;f++)a.push([]);f=0;for(i=c.length;f<i;f++){d=c[f];for(e=d.firstChild;e;){if("TD"==e.nodeName.toUpperCase()||"TH"==e.nodeName.toUpperCase()){l=1*e.getAttribute("colspan");
q=1*e.getAttribute("rowspan");l=!l||0===l||1===l?1:l;q=!q||0===q||1===q?1:q;g=0;for(j=a[f];j[g];)g++;n=g;t=1===l?!0:!1;for(j=0;j<l;j++)for(g=0;g<q;g++)a[f+g][n+j]={cell:e,unique:t},a[f+g].nTr=d}e=e.nextSibling}}}function qa(a,b,c){var d=[];c||(c=a.aoHeader,b&&(c=[],da(c,b)));for(var b=0,e=c.length;b<e;b++)for(var f=0,g=c[b].length;f<g;f++)if(c[b][f].unique&&(!d[f]||!a.bSortCellsTop))d[f]=c[b][f].cell;return d}function ra(a,b,c){u(a,"aoServerParams","serverParams",[b]);if(b&&h.isArray(b)){var d={},
e=/(.*?)\[\]$/;h.each(b,function(a,b){var c=b.name.match(e);c?(c=c[0],d[c]||(d[c]=[]),d[c].push(b.value)):d[b.name]=b.value});b=d}var f,g=a.ajax,j=a.oInstance,i=function(b){u(a,null,"xhr",[a,b,a.jqXHR]);c(b)};if(h.isPlainObject(g)&&g.data){f=g.data;var n=h.isFunction(f)?f(b,a):f,b=h.isFunction(f)&&n?n:h.extend(!0,b,n);delete g.data}n={data:b,success:function(b){var c=b.error||b.sError;c&&L(a,0,c);a.json=b;i(b)},dataType:"json",cache:!1,type:a.sServerMethod,error:function(b,c){var d=u(a,null,"xhr",
[a,null,a.jqXHR]);-1===h.inArray(!0,d)&&("parsererror"==c?L(a,0,"Invalid JSON response",1):4===b.readyState&&L(a,0,"Ajax error",7));C(a,!1)}};a.oAjaxData=b;u(a,null,"preXhr",[a,b]);a.fnServerData?a.fnServerData.call(j,a.sAjaxSource,h.map(b,function(a,b){return{name:b,value:a}}),i,a):a.sAjaxSource||"string"===typeof g?a.jqXHR=h.ajax(h.extend(n,{url:g||a.sAjaxSource})):h.isFunction(g)?a.jqXHR=g.call(j,b,i,a):(a.jqXHR=h.ajax(h.extend(n,g)),g.data=f)}function lb(a){return a.bAjaxDataGet?(a.iDraw++,C(a,
!0),ra(a,ub(a),function(b){vb(a,b)}),!1):!0}function ub(a){var b=a.aoColumns,c=b.length,d=a.oFeatures,e=a.oPreviousSearch,f=a.aoPreSearchCols,g,j=[],i,n,l,q=V(a);g=a._iDisplayStart;i=!1!==d.bPaginate?a._iDisplayLength:-1;var k=function(a,b){j.push({name:a,value:b})};k("sEcho",a.iDraw);k("iColumns",c);k("sColumns",G(b,"sName").join(","));k("iDisplayStart",g);k("iDisplayLength",i);var S={draw:a.iDraw,columns:[],order:[],start:g,length:i,search:{value:e.sSearch,regex:e.bRegex}};for(g=0;g<c;g++)n=b[g],
l=f[g],i="function"==typeof n.mData?"function":n.mData,S.columns.push({data:i,name:n.sName,searchable:n.bSearchable,orderable:n.bSortable,search:{value:l.sSearch,regex:l.bRegex}}),k("mDataProp_"+g,i),d.bFilter&&(k("sSearch_"+g,l.sSearch),k("bRegex_"+g,l.bRegex),k("bSearchable_"+g,n.bSearchable)),d.bSort&&k("bSortable_"+g,n.bSortable);d.bFilter&&(k("sSearch",e.sSearch),k("bRegex",e.bRegex));d.bSort&&(h.each(q,function(a,b){S.order.push({column:b.col,dir:b.dir});k("iSortCol_"+a,b.col);k("sSortDir_"+
a,b.dir)}),k("iSortingCols",q.length));b=m.ext.legacy.ajax;return null===b?a.sAjaxSource?j:S:b?j:S}function vb(a,b){var c=sa(a,b),d=b.sEcho!==k?b.sEcho:b.draw,e=b.iTotalRecords!==k?b.iTotalRecords:b.recordsTotal,f=b.iTotalDisplayRecords!==k?b.iTotalDisplayRecords:b.recordsFiltered;if(d){if(1*d<a.iDraw)return;a.iDraw=1*d}na(a);a._iRecordsTotal=parseInt(e,10);a._iRecordsDisplay=parseInt(f,10);d=0;for(e=c.length;d<e;d++)N(a,c[d]);a.aiDisplay=a.aiDisplayMaster.slice();a.bAjaxDataGet=!1;O(a);a._bInitComplete||
ta(a,b);a.bAjaxDataGet=!0;C(a,!1)}function sa(a,b){var c=h.isPlainObject(a.ajax)&&a.ajax.dataSrc!==k?a.ajax.dataSrc:a.sAjaxDataProp;return"data"===c?b.aaData||b[c]:""!==c?Q(c)(b):b}function pb(a){var b=a.oClasses,c=a.sTableId,d=a.oLanguage,e=a.oPreviousSearch,f=a.aanFeatures,g='<input type="search" class="'+b.sFilterInput+'"/>',j=d.sSearch,j=j.match(/_INPUT_/)?j.replace("_INPUT_",g):j+g,b=h("<div/>",{id:!f.f?c+"_filter":null,"class":b.sFilter}).append(h("<label/>").append(j)),f=function(){var b=!this.value?
"":this.value;b!=e.sSearch&&(fa(a,{sSearch:b,bRegex:e.bRegex,bSmart:e.bSmart,bCaseInsensitive:e.bCaseInsensitive}),a._iDisplayStart=0,O(a))},g=null!==a.searchDelay?a.searchDelay:"ssp"===y(a)?400:0,i=h("input",b).val(e.sSearch).attr("placeholder",d.sSearchPlaceholder).bind("keyup.DT search.DT input.DT paste.DT cut.DT",g?Oa(f,g):f).bind("keypress.DT",function(a){if(13==a.keyCode)return!1}).attr("aria-controls",c);h(a.nTable).on("search.dt.DT",function(b,c){if(a===c)try{i[0]!==I.activeElement&&i.val(e.sSearch)}catch(d){}});
return b[0]}function fa(a,b,c){var d=a.oPreviousSearch,e=a.aoPreSearchCols,f=function(a){d.sSearch=a.sSearch;d.bRegex=a.bRegex;d.bSmart=a.bSmart;d.bCaseInsensitive=a.bCaseInsensitive};Ga(a);if("ssp"!=y(a)){wb(a,b.sSearch,c,b.bEscapeRegex!==k?!b.bEscapeRegex:b.bRegex,b.bSmart,b.bCaseInsensitive);f(b);for(b=0;b<e.length;b++)xb(a,e[b].sSearch,b,e[b].bEscapeRegex!==k?!e[b].bEscapeRegex:e[b].bRegex,e[b].bSmart,e[b].bCaseInsensitive);yb(a)}else f(b);a.bFiltered=!0;u(a,null,"search",[a])}function yb(a){for(var b=
m.ext.search,c=a.aiDisplay,d,e,f=0,g=b.length;f<g;f++){for(var j=[],i=0,n=c.length;i<n;i++)e=c[i],d=a.aoData[e],b[f](a,d._aFilterData,e,d._aData,i)&&j.push(e);c.length=0;h.merge(c,j)}}function xb(a,b,c,d,e,f){if(""!==b)for(var g=a.aiDisplay,d=Pa(b,d,e,f),e=g.length-1;0<=e;e--)b=a.aoData[g[e]]._aFilterData[c],d.test(b)||g.splice(e,1)}function wb(a,b,c,d,e,f){var d=Pa(b,d,e,f),e=a.oPreviousSearch.sSearch,f=a.aiDisplayMaster,g;0!==m.ext.search.length&&(c=!0);g=zb(a);if(0>=b.length)a.aiDisplay=f.slice();
else{if(g||c||e.length>b.length||0!==b.indexOf(e)||a.bSorted)a.aiDisplay=f.slice();b=a.aiDisplay;for(c=b.length-1;0<=c;c--)d.test(a.aoData[b[c]]._sFilterRow)||b.splice(c,1)}}function Pa(a,b,c,d){a=b?a:Qa(a);c&&(a="^(?=.*?"+h.map(a.match(/"[^"]+"|[^ ]+/g)||[""],function(a){if('"'===a.charAt(0))var b=a.match(/^"(.*)"$/),a=b?b[1]:a;return a.replace('"',"")}).join(")(?=.*?")+").*$");return RegExp(a,d?"i":"")}function zb(a){var b=a.aoColumns,c,d,e,f,g,j,i,h,l=m.ext.type.search;c=!1;d=0;for(f=a.aoData.length;d<
f;d++)if(h=a.aoData[d],!h._aFilterData){j=[];e=0;for(g=b.length;e<g;e++)c=b[e],c.bSearchable?(i=B(a,d,e,"filter"),l[c.sType]&&(i=l[c.sType](i)),null===i&&(i=""),"string"!==typeof i&&i.toString&&(i=i.toString())):i="",i.indexOf&&-1!==i.indexOf("&")&&(ua.innerHTML=i,i=Zb?ua.textContent:ua.innerText),i.replace&&(i=i.replace(/[\r\n]/g,"")),j.push(i);h._aFilterData=j;h._sFilterRow=j.join("  ");c=!0}return c}function Ab(a){return{search:a.sSearch,smart:a.bSmart,regex:a.bRegex,caseInsensitive:a.bCaseInsensitive}}
function Bb(a){return{sSearch:a.search,bSmart:a.smart,bRegex:a.regex,bCaseInsensitive:a.caseInsensitive}}function sb(a){var b=a.sTableId,c=a.aanFeatures.i,d=h("<div/>",{"class":a.oClasses.sInfo,id:!c?b+"_info":null});c||(a.aoDrawCallback.push({fn:Cb,sName:"information"}),d.attr("role","status").attr("aria-live","polite"),h(a.nTable).attr("aria-describedby",b+"_info"));return d[0]}function Cb(a){var b=a.aanFeatures.i;if(0!==b.length){var c=a.oLanguage,d=a._iDisplayStart+1,e=a.fnDisplayEnd(),f=a.fnRecordsTotal(),
g=a.fnRecordsDisplay(),j=g?c.sInfo:c.sInfoEmpty;g!==f&&(j+=" "+c.sInfoFiltered);j+=c.sInfoPostFix;j=Db(a,j);c=c.fnInfoCallback;null!==c&&(j=c.call(a.oInstance,a,d,e,f,g,j));h(b).html(j)}}function Db(a,b){var c=a.fnFormatNumber,d=a._iDisplayStart+1,e=a._iDisplayLength,f=a.fnRecordsDisplay(),g=-1===e;return b.replace(/_START_/g,c.call(a,d)).replace(/_END_/g,c.call(a,a.fnDisplayEnd())).replace(/_MAX_/g,c.call(a,a.fnRecordsTotal())).replace(/_TOTAL_/g,c.call(a,f)).replace(/_PAGE_/g,c.call(a,g?1:Math.ceil(d/
e))).replace(/_PAGES_/g,c.call(a,g?1:Math.ceil(f/e)))}function ga(a){var b,c,d=a.iInitDisplayStart,e=a.aoColumns,f;c=a.oFeatures;var g=a.bDeferLoading;if(a.bInitialised){nb(a);kb(a);ea(a,a.aoHeader);ea(a,a.aoFooter);C(a,!0);c.bAutoWidth&&Fa(a);b=0;for(c=e.length;b<c;b++)f=e[b],f.sWidth&&(f.nTh.style.width=x(f.sWidth));u(a,null,"preInit",[a]);T(a);e=y(a);if("ssp"!=e||g)"ajax"==e?ra(a,[],function(c){var f=sa(a,c);for(b=0;b<f.length;b++)N(a,f[b]);a.iInitDisplayStart=d;T(a);C(a,!1);ta(a,c)},a):(C(a,!1),
ta(a))}else setTimeout(function(){ga(a)},200)}function ta(a,b){a._bInitComplete=!0;(b||a.oInit.aaData)&&Y(a);u(a,null,"plugin-init",[a,b]);u(a,"aoInitComplete","init",[a,b])}function Ra(a,b){var c=parseInt(b,10);a._iDisplayLength=c;Sa(a);u(a,null,"length",[a,c])}function ob(a){for(var b=a.oClasses,c=a.sTableId,d=a.aLengthMenu,e=h.isArray(d[0]),f=e?d[0]:d,d=e?d[1]:d,e=h("<select/>",{name:c+"_length","aria-controls":c,"class":b.sLengthSelect}),g=0,j=f.length;g<j;g++)e[0][g]=new Option(d[g],f[g]);var i=
h("<div><label/></div>").addClass(b.sLength);a.aanFeatures.l||(i[0].id=c+"_length");i.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",e[0].outerHTML));h("select",i).val(a._iDisplayLength).bind("change.DT",function(){Ra(a,h(this).val());O(a)});h(a.nTable).bind("length.dt.DT",function(b,c,d){a===c&&h("select",i).val(d)});return i[0]}function tb(a){var b=a.sPaginationType,c=m.ext.pager[b],d="function"===typeof c,e=function(a){O(a)},b=h("<div/>").addClass(a.oClasses.sPaging+b)[0],f=a.aanFeatures;
d||c.fnInit(a,b,e);f.p||(b.id=a.sTableId+"_paginate",a.aoDrawCallback.push({fn:function(a){if(d){var b=a._iDisplayStart,i=a._iDisplayLength,h=a.fnRecordsDisplay(),l=-1===i,b=l?0:Math.ceil(b/i),i=l?1:Math.ceil(h/i),h=c(b,i),k,l=0;for(k=f.p.length;l<k;l++)Na(a,"pageButton")(a,f.p[l],l,h,b,i)}else c.fnUpdate(a,e)},sName:"pagination"}));return b}function Ta(a,b,c){var d=a._iDisplayStart,e=a._iDisplayLength,f=a.fnRecordsDisplay();0===f||-1===e?d=0:"number"===typeof b?(d=b*e,d>f&&(d=0)):"first"==b?d=0:
"previous"==b?(d=0<=e?d-e:0,0>d&&(d=0)):"next"==b?d+e<f&&(d+=e):"last"==b?d=Math.floor((f-1)/e)*e:L(a,0,"Unknown paging action: "+b,5);b=a._iDisplayStart!==d;a._iDisplayStart=d;b&&(u(a,null,"page",[a]),c&&O(a));return b}function qb(a){return h("<div/>",{id:!a.aanFeatures.r?a.sTableId+"_processing":null,"class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function C(a,b){a.oFeatures.bProcessing&&h(a.aanFeatures.r).css("display",b?"block":"none");u(a,null,"processing",
[a,b])}function rb(a){var b=h(a.nTable);b.attr("role","grid");var c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var d=c.sX,e=c.sY,f=a.oClasses,g=b.children("caption"),j=g.length?g[0]._captionSide:null,i=h(b[0].cloneNode(!1)),n=h(b[0].cloneNode(!1)),l=b.children("tfoot");l.length||(l=null);i=h("<div/>",{"class":f.sScrollWrapper}).append(h("<div/>",{"class":f.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,width:d?!d?null:x(d):"100%"}).append(h("<div/>",{"class":f.sScrollHeadInner}).css({"box-sizing":"content-box",
width:c.sXInner||"100%"}).append(i.removeAttr("id").css("margin-left",0).append("top"===j?g:null).append(b.children("thead"))))).append(h("<div/>",{"class":f.sScrollBody}).css({position:"relative",overflow:"auto",width:!d?null:x(d)}).append(b));l&&i.append(h("<div/>",{"class":f.sScrollFoot}).css({overflow:"hidden",border:0,width:d?!d?null:x(d):"100%"}).append(h("<div/>",{"class":f.sScrollFootInner}).append(n.removeAttr("id").css("margin-left",0).append("bottom"===j?g:null).append(b.children("tfoot")))));
var b=i.children(),k=b[0],f=b[1],t=l?b[2]:null;if(d)h(f).on("scroll.DT",function(){var a=this.scrollLeft;k.scrollLeft=a;l&&(t.scrollLeft=a)});h(f).css(e&&c.bCollapse?"max-height":"height",e);a.nScrollHead=k;a.nScrollBody=f;a.nScrollFoot=t;a.aoDrawCallback.push({fn:ka,sName:"scrolling"});return i[0]}function ka(a){var b=a.oScroll,c=b.sX,d=b.sXInner,e=b.sY,b=b.iBarWidth,f=h(a.nScrollHead),g=f[0].style,j=f.children("div"),i=j[0].style,n=j.children("table"),j=a.nScrollBody,l=h(j),q=j.style,t=h(a.nScrollFoot).children("div"),
m=t.children("table"),o=h(a.nTHead),F=h(a.nTable),p=F[0],r=p.style,u=a.nTFoot?h(a.nTFoot):null,Eb=a.oBrowser,Ua=Eb.bScrollOversize,s=G(a.aoColumns,"nTh"),P,v,w,y,z=[],A=[],B=[],C=[],D,E=function(a){a=a.style;a.paddingTop="0";a.paddingBottom="0";a.borderTopWidth="0";a.borderBottomWidth="0";a.height=0};v=j.scrollHeight>j.clientHeight;if(a.scrollBarVis!==v&&a.scrollBarVis!==k)a.scrollBarVis=v,Y(a);else{a.scrollBarVis=v;F.children("thead, tfoot").remove();u&&(w=u.clone().prependTo(F),P=u.find("tr"),w=
w.find("tr"));y=o.clone().prependTo(F);o=o.find("tr");v=y.find("tr");y.find("th, td").removeAttr("tabindex");c||(q.width="100%",f[0].style.width="100%");h.each(qa(a,y),function(b,c){D=Z(a,b);c.style.width=a.aoColumns[D].sWidth});u&&J(function(a){a.style.width=""},w);f=F.outerWidth();if(""===c){r.width="100%";if(Ua&&(F.find("tbody").height()>j.offsetHeight||"scroll"==l.css("overflow-y")))r.width=x(F.outerWidth()-b);f=F.outerWidth()}else""!==d&&(r.width=x(d),f=F.outerWidth());J(E,v);J(function(a){B.push(a.innerHTML);
z.push(x(h(a).css("width")))},v);J(function(a,b){if(h.inArray(a,s)!==-1)a.style.width=z[b]},o);h(v).height(0);u&&(J(E,w),J(function(a){C.push(a.innerHTML);A.push(x(h(a).css("width")))},w),J(function(a,b){a.style.width=A[b]},P),h(w).height(0));J(function(a,b){a.innerHTML='<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+B[b]+"</div>";a.style.width=z[b]},v);u&&J(function(a,b){a.innerHTML='<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+C[b]+"</div>";a.style.width=
A[b]},w);if(F.outerWidth()<f){P=j.scrollHeight>j.offsetHeight||"scroll"==l.css("overflow-y")?f+b:f;if(Ua&&(j.scrollHeight>j.offsetHeight||"scroll"==l.css("overflow-y")))r.width=x(P-b);(""===c||""!==d)&&L(a,1,"Possible column misalignment",6)}else P="100%";q.width=x(P);g.width=x(P);u&&(a.nScrollFoot.style.width=x(P));!e&&Ua&&(q.height=x(p.offsetHeight+b));c=F.outerWidth();n[0].style.width=x(c);i.width=x(c);d=F.height()>j.clientHeight||"scroll"==l.css("overflow-y");e="padding"+(Eb.bScrollbarLeft?"Left":
"Right");i[e]=d?b+"px":"0px";u&&(m[0].style.width=x(c),t[0].style.width=x(c),t[0].style[e]=d?b+"px":"0px");F.children("colgroup").insertBefore(F.children("thead"));l.scroll();if((a.bSorted||a.bFiltered)&&!a._drawHold)j.scrollTop=0}}function J(a,b,c){for(var d=0,e=0,f=b.length,g,j;e<f;){g=b[e].firstChild;for(j=c?c[e].firstChild:null;g;)1===g.nodeType&&(c?a(g,j,d):a(g,d),d++),g=g.nextSibling,j=c?j.nextSibling:null;e++}}function Fa(a){var b=a.nTable,c=a.aoColumns,d=a.oScroll,e=d.sY,f=d.sX,g=d.sXInner,
j=c.length,i=la(a,"bVisible"),n=h("th",a.nTHead),l=b.getAttribute("width"),k=b.parentNode,t=!1,m,o,p=a.oBrowser,d=p.bScrollOversize;(m=b.style.width)&&-1!==m.indexOf("%")&&(l=m);for(m=0;m<i.length;m++)o=c[i[m]],null!==o.sWidth&&(o.sWidth=Fb(o.sWidthOrig,k),t=!0);if(d||!t&&!f&&!e&&j==aa(a)&&j==n.length)for(m=0;m<j;m++)i=Z(a,m),null!==i&&(c[i].sWidth=x(n.eq(m).width()));else{j=h(b).clone().css("visibility","hidden").removeAttr("id");j.find("tbody tr").remove();var r=h("<tr/>").appendTo(j.find("tbody"));
j.find("thead, tfoot").remove();j.append(h(a.nTHead).clone()).append(h(a.nTFoot).clone());j.find("tfoot th, tfoot td").css("width","");n=qa(a,j.find("thead")[0]);for(m=0;m<i.length;m++)o=c[i[m]],n[m].style.width=null!==o.sWidthOrig&&""!==o.sWidthOrig?x(o.sWidthOrig):"",o.sWidthOrig&&f&&h(n[m]).append(h("<div/>").css({width:o.sWidthOrig,margin:0,padding:0,border:0,height:1}));if(a.aoData.length)for(m=0;m<i.length;m++)t=i[m],o=c[t],h(Gb(a,t)).clone(!1).append(o.sContentPadding).appendTo(r);h("[name]",
j).removeAttr("name");o=h("<div/>").css(f||e?{position:"absolute",top:0,left:0,height:1,right:0,overflow:"hidden"}:{}).append(j).appendTo(k);f&&g?j.width(g):f?(j.css("width","auto"),j.removeAttr("width"),j.width()<k.clientWidth&&l&&j.width(k.clientWidth)):e?j.width(k.clientWidth):l&&j.width(l);for(m=e=0;m<i.length;m++)k=h(n[m]),g=k.outerWidth()-k.width(),k=p.bBounding?Math.ceil(n[m].getBoundingClientRect().width):k.outerWidth(),e+=k,c[i[m]].sWidth=x(k-g);b.style.width=x(e);o.remove()}l&&(b.style.width=
x(l));if((l||f)&&!a._reszEvt)b=function(){h(D).bind("resize.DT-"+a.sInstance,Oa(function(){Y(a)}))},d?setTimeout(b,1E3):b(),a._reszEvt=!0}function Fb(a,b){if(!a)return 0;var c=h("<div/>").css("width",x(a)).appendTo(b||I.body),d=c[0].offsetWidth;c.remove();return d}function Gb(a,b){var c=Hb(a,b);if(0>c)return null;var d=a.aoData[c];return!d.nTr?h("<td/>").html(B(a,c,b,"display"))[0]:d.anCells[b]}function Hb(a,b){for(var c,d=-1,e=-1,f=0,g=a.aoData.length;f<g;f++)c=B(a,f,b,"display")+"",c=c.replace($b,
""),c=c.replace(/&nbsp;/g," "),c.length>d&&(d=c.length,e=f);return e}function x(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function V(a){var b,c,d=[],e=a.aoColumns,f,g,j,i;b=a.aaSortingFixed;c=h.isPlainObject(b);var n=[];f=function(a){a.length&&!h.isArray(a[0])?n.push(a):h.merge(n,a)};h.isArray(b)&&f(b);c&&b.pre&&f(b.pre);f(a.aaSorting);c&&b.post&&f(b.post);for(a=0;a<n.length;a++){i=n[a][0];f=e[i].aDataSort;b=0;for(c=f.length;b<c;b++)g=f[b],j=e[g].sType||
"string",n[a]._idx===k&&(n[a]._idx=h.inArray(n[a][1],e[g].asSorting)),d.push({src:i,col:g,dir:n[a][1],index:n[a]._idx,type:j,formatter:m.ext.type.order[j+"-pre"]})}return d}function mb(a){var b,c,d=[],e=m.ext.type.order,f=a.aoData,g=0,j,i=a.aiDisplayMaster,h;Ga(a);h=V(a);b=0;for(c=h.length;b<c;b++)j=h[b],j.formatter&&g++,Ib(a,j.col);if("ssp"!=y(a)&&0!==h.length){b=0;for(c=i.length;b<c;b++)d[i[b]]=b;g===h.length?i.sort(function(a,b){var c,e,g,j,i=h.length,k=f[a]._aSortData,m=f[b]._aSortData;for(g=
0;g<i;g++)if(j=h[g],c=k[j.col],e=m[j.col],c=c<e?-1:c>e?1:0,0!==c)return"asc"===j.dir?c:-c;c=d[a];e=d[b];return c<e?-1:c>e?1:0}):i.sort(function(a,b){var c,g,j,i,k=h.length,m=f[a]._aSortData,p=f[b]._aSortData;for(j=0;j<k;j++)if(i=h[j],c=m[i.col],g=p[i.col],i=e[i.type+"-"+i.dir]||e["string-"+i.dir],c=i(c,g),0!==c)return c;c=d[a];g=d[b];return c<g?-1:c>g?1:0})}a.bSorted=!0}function Jb(a){for(var b,c,d=a.aoColumns,e=V(a),a=a.oLanguage.oAria,f=0,g=d.length;f<g;f++){c=d[f];var j=c.asSorting;b=c.sTitle.replace(/<.*?>/g,
"");var i=c.nTh;i.removeAttribute("aria-sort");c.bSortable&&(0<e.length&&e[0].col==f?(i.setAttribute("aria-sort","asc"==e[0].dir?"ascending":"descending"),c=j[e[0].index+1]||j[0]):c=j[0],b+="asc"===c?a.sSortAscending:a.sSortDescending);i.setAttribute("aria-label",b)}}function Va(a,b,c,d){var e=a.aaSorting,f=a.aoColumns[b].asSorting,g=function(a,b){var c=a._idx;c===k&&(c=h.inArray(a[1],f));return c+1<f.length?c+1:b?null:0};"number"===typeof e[0]&&(e=a.aaSorting=[e]);c&&a.oFeatures.bSortMulti?(c=h.inArray(b,
G(e,"0")),-1!==c?(b=g(e[c],!0),null===b&&1===e.length&&(b=0),null===b?e.splice(c,1):(e[c][1]=f[b],e[c]._idx=b)):(e.push([b,f[0],0]),e[e.length-1]._idx=0)):e.length&&e[0][0]==b?(b=g(e[0]),e.length=1,e[0][1]=f[b],e[0]._idx=b):(e.length=0,e.push([b,f[0]]),e[0]._idx=0);T(a);"function"==typeof d&&d(a)}function Ma(a,b,c,d){var e=a.aoColumns[c];Wa(b,{},function(b){!1!==e.bSortable&&(a.oFeatures.bProcessing?(C(a,!0),setTimeout(function(){Va(a,c,b.shiftKey,d);"ssp"!==y(a)&&C(a,!1)},0)):Va(a,c,b.shiftKey,d))})}
function va(a){var b=a.aLastSort,c=a.oClasses.sSortColumn,d=V(a),e=a.oFeatures,f,g;if(e.bSort&&e.bSortClasses){e=0;for(f=b.length;e<f;e++)g=b[e].src,h(G(a.aoData,"anCells",g)).removeClass(c+(2>e?e+1:3));e=0;for(f=d.length;e<f;e++)g=d[e].src,h(G(a.aoData,"anCells",g)).addClass(c+(2>e?e+1:3))}a.aLastSort=d}function Ib(a,b){var c=a.aoColumns[b],d=m.ext.order[c.sSortDataType],e;d&&(e=d.call(a.oInstance,a,b,$(a,b)));for(var f,g=m.ext.type.order[c.sType+"-pre"],j=0,i=a.aoData.length;j<i;j++)if(c=a.aoData[j],
c._aSortData||(c._aSortData=[]),!c._aSortData[b]||d)f=d?e[j]:B(a,j,b,"sort"),c._aSortData[b]=g?g(f):f}function wa(a){if(a.oFeatures.bStateSave&&!a.bDestroying){var b={time:+new Date,start:a._iDisplayStart,length:a._iDisplayLength,order:h.extend(!0,[],a.aaSorting),search:Ab(a.oPreviousSearch),columns:h.map(a.aoColumns,function(b,d){return{visible:b.bVisible,search:Ab(a.aoPreSearchCols[d])}})};u(a,"aoStateSaveParams","stateSaveParams",[a,b]);a.oSavedState=b;a.fnStateSaveCallback.call(a.oInstance,a,
b)}}function Kb(a){var b,c,d=a.aoColumns;if(a.oFeatures.bStateSave){var e=a.fnStateLoadCallback.call(a.oInstance,a);if(e&&e.time&&(b=u(a,"aoStateLoadParams","stateLoadParams",[a,e]),-1===h.inArray(!1,b)&&(b=a.iStateDuration,!(0<b&&e.time<+new Date-1E3*b)&&d.length===e.columns.length))){a.oLoadedState=h.extend(!0,{},e);e.start!==k&&(a._iDisplayStart=e.start,a.iInitDisplayStart=e.start);e.length!==k&&(a._iDisplayLength=e.length);e.order!==k&&(a.aaSorting=[],h.each(e.order,function(b,c){a.aaSorting.push(c[0]>=
d.length?[0,c[1]]:c)}));e.search!==k&&h.extend(a.oPreviousSearch,Bb(e.search));b=0;for(c=e.columns.length;b<c;b++){var f=e.columns[b];f.visible!==k&&(d[b].bVisible=f.visible);f.search!==k&&h.extend(a.aoPreSearchCols[b],Bb(f.search))}u(a,"aoStateLoaded","stateLoaded",[a,e])}}}function xa(a){var b=m.settings,a=h.inArray(a,G(b,"nTable"));return-1!==a?b[a]:null}function L(a,b,c,d){c="DataTables warning: "+(a?"table id="+a.sTableId+" - ":"")+c;d&&(c+=". For more information about this error, please see http://datatables.net/tn/"+
d);if(b)D.console&&console.log&&console.log(c);else if(b=m.ext,b=b.sErrMode||b.errMode,a&&u(a,null,"error",[a,d,c]),"alert"==b)alert(c);else{if("throw"==b)throw Error(c);"function"==typeof b&&b(a,d,c)}}function E(a,b,c,d){h.isArray(c)?h.each(c,function(c,d){h.isArray(d)?E(a,b,d[0],d[1]):E(a,b,d)}):(d===k&&(d=c),b[c]!==k&&(a[d]=b[c]))}function Lb(a,b,c){var d,e;for(e in b)b.hasOwnProperty(e)&&(d=b[e],h.isPlainObject(d)?(h.isPlainObject(a[e])||(a[e]={}),h.extend(!0,a[e],d)):a[e]=c&&"data"!==e&&"aaData"!==
e&&h.isArray(d)?d.slice():d);return a}function Wa(a,b,c){h(a).bind("click.DT",b,function(b){a.blur();c(b)}).bind("keypress.DT",b,function(a){13===a.which&&(a.preventDefault(),c(a))}).bind("selectstart.DT",function(){return!1})}function z(a,b,c,d){c&&a[b].push({fn:c,sName:d})}function u(a,b,c,d){var e=[];b&&(e=h.map(a[b].slice().reverse(),function(b){return b.fn.apply(a.oInstance,d)}));null!==c&&(b=h.Event(c+".dt"),h(a.nTable).trigger(b,d),e.push(b.result));return e}function Sa(a){var b=a._iDisplayStart,
c=a.fnDisplayEnd(),d=a._iDisplayLength;b>=c&&(b=c-d);b-=b%d;if(-1===d||0>b)b=0;a._iDisplayStart=b}function Na(a,b){var c=a.renderer,d=m.ext.renderer[b];return h.isPlainObject(c)&&c[b]?d[c[b]]||d._:"string"===typeof c?d[c]||d._:d._}function y(a){return a.oFeatures.bServerSide?"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function ya(a,b){var c=[],c=Mb.numbers_length,d=Math.floor(c/2);b<=c?c=W(0,b):a<=d?(c=W(0,c-2),c.push("ellipsis"),c.push(b-1)):(a>=b-1-d?c=W(b-(c-2),b):(c=W(a-d+2,a+d-1),c.push("ellipsis"),
c.push(b-1)),c.splice(0,0,"ellipsis"),c.splice(0,0,0));c.DT_el="span";return c}function db(a){h.each({num:function(b){return za(b,a)},"num-fmt":function(b){return za(b,a,Xa)},"html-num":function(b){return za(b,a,Aa)},"html-num-fmt":function(b){return za(b,a,Aa,Xa)}},function(b,c){v.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(v.type.search[b+a]=v.type.search.html)})}function Nb(a){return function(){var b=[xa(this[m.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return m.ext.internal[a].apply(this,
b)}}var m=function(a){this.$=function(a,b){return this.api(!0).$(a,b)};this._=function(a,b){return this.api(!0).rows(a,b).data()};this.api=function(a){return a?new r(xa(this[v.iApiIndex])):new r(this)};this.fnAddData=function(a,b){var c=this.api(!0),d=h.isArray(a)&&(h.isArray(a[0])||h.isPlainObject(a[0]))?c.rows.add(a):c.row.add(a);(b===k||b)&&c.draw();return d.flatten().toArray()};this.fnAdjustColumnSizing=function(a){var b=this.api(!0).columns.adjust(),c=b.settings()[0],d=c.oScroll;a===k||a?b.draw(!1):
(""!==d.sX||""!==d.sY)&&ka(c)};this.fnClearTable=function(a){var b=this.api(!0).clear();(a===k||a)&&b.draw()};this.fnClose=function(a){this.api(!0).row(a).child.hide()};this.fnDeleteRow=function(a,b,c){var d=this.api(!0),a=d.rows(a),e=a.settings()[0],h=e.aoData[a[0][0]];a.remove();b&&b.call(this,e,h);(c===k||c)&&d.draw();return h};this.fnDestroy=function(a){this.api(!0).destroy(a)};this.fnDraw=function(a){this.api(!0).draw(a)};this.fnFilter=function(a,b,c,d,e,h){e=this.api(!0);null===b||b===k?e.search(a,
c,d,h):e.column(b).search(a,c,d,h);e.draw()};this.fnGetData=function(a,b){var c=this.api(!0);if(a!==k){var d=a.nodeName?a.nodeName.toLowerCase():"";return b!==k||"td"==d||"th"==d?c.cell(a,b).data():c.row(a).data()||null}return c.data().toArray()};this.fnGetNodes=function(a){var b=this.api(!0);return a!==k?b.row(a).node():b.rows().nodes().flatten().toArray()};this.fnGetPosition=function(a){var b=this.api(!0),c=a.nodeName.toUpperCase();return"TR"==c?b.row(a).index():"TD"==c||"TH"==c?(a=b.cell(a).index(),
[a.row,a.columnVisible,a.column]):null};this.fnIsOpen=function(a){return this.api(!0).row(a).child.isShown()};this.fnOpen=function(a,b,c){return this.api(!0).row(a).child(b,c).show().child()[0]};this.fnPageChange=function(a,b){var c=this.api(!0).page(a);(b===k||b)&&c.draw(!1)};this.fnSetColumnVis=function(a,b,c){a=this.api(!0).column(a).visible(b);(c===k||c)&&a.columns.adjust().draw()};this.fnSettings=function(){return xa(this[v.iApiIndex])};this.fnSort=function(a){this.api(!0).order(a).draw()};this.fnSortListener=
function(a,b,c){this.api(!0).order.listener(a,b,c)};this.fnUpdate=function(a,b,c,d,e){var h=this.api(!0);c===k||null===c?h.row(b).data(a):h.cell(b,c).data(a);(e===k||e)&&h.columns.adjust();(d===k||d)&&h.draw();return 0};this.fnVersionCheck=v.fnVersionCheck;var b=this,c=a===k,d=this.length;c&&(a={});this.oApi=this.internal=v.internal;for(var e in m.ext.internal)e&&(this[e]=Nb(e));this.each(function(){var e={},e=1<d?Lb(e,a,!0):a,g=0,j,i=this.getAttribute("id"),n=!1,l=m.defaults,q=h(this);if("table"!=
this.nodeName.toLowerCase())L(null,0,"Non-table node initialisation ("+this.nodeName+")",2);else{eb(l);fb(l.column);K(l,l,!0);K(l.column,l.column,!0);K(l,h.extend(e,q.data()));var t=m.settings,g=0;for(j=t.length;g<j;g++){var p=t[g];if(p.nTable==this||p.nTHead.parentNode==this||p.nTFoot&&p.nTFoot.parentNode==this){g=e.bRetrieve!==k?e.bRetrieve:l.bRetrieve;if(c||g)return p.oInstance;if(e.bDestroy!==k?e.bDestroy:l.bDestroy){p.oInstance.fnDestroy();break}else{L(p,0,"Cannot reinitialise DataTable",3);
return}}if(p.sTableId==this.id){t.splice(g,1);break}}if(null===i||""===i)this.id=i="DataTables_Table_"+m.ext._unique++;var o=h.extend(!0,{},m.models.oSettings,{sDestroyWidth:q[0].style.width,sInstance:i,sTableId:i});o.nTable=this;o.oApi=b.internal;o.oInit=e;t.push(o);o.oInstance=1===b.length?b:q.dataTable();eb(e);e.oLanguage&&Da(e.oLanguage);e.aLengthMenu&&!e.iDisplayLength&&(e.iDisplayLength=h.isArray(e.aLengthMenu[0])?e.aLengthMenu[0][0]:e.aLengthMenu[0]);e=Lb(h.extend(!0,{},l),e);E(o.oFeatures,
e,"bPaginate bLengthChange bFilter bSort bSortMulti bInfo bProcessing bAutoWidth bSortClasses bServerSide bDeferRender".split(" "));E(o,e,["asStripeClasses","ajax","fnServerData","fnFormatNumber","sServerMethod","aaSorting","aaSortingFixed","aLengthMenu","sPaginationType","sAjaxSource","sAjaxDataProp","iStateDuration","sDom","bSortCellsTop","iTabIndex","fnStateLoadCallback","fnStateSaveCallback","renderer","searchDelay","rowId",["iCookieDuration","iStateDuration"],["oSearch","oPreviousSearch"],["aoSearchCols",
"aoPreSearchCols"],["iDisplayLength","_iDisplayLength"],["bJQueryUI","bJUI"]]);E(o.oScroll,e,[["sScrollX","sX"],["sScrollXInner","sXInner"],["sScrollY","sY"],["bScrollCollapse","bCollapse"]]);E(o.oLanguage,e,"fnInfoCallback");z(o,"aoDrawCallback",e.fnDrawCallback,"user");z(o,"aoServerParams",e.fnServerParams,"user");z(o,"aoStateSaveParams",e.fnStateSaveParams,"user");z(o,"aoStateLoadParams",e.fnStateLoadParams,"user");z(o,"aoStateLoaded",e.fnStateLoaded,"user");z(o,"aoRowCallback",e.fnRowCallback,
"user");z(o,"aoRowCreatedCallback",e.fnCreatedRow,"user");z(o,"aoHeaderCallback",e.fnHeaderCallback,"user");z(o,"aoFooterCallback",e.fnFooterCallback,"user");z(o,"aoInitComplete",e.fnInitComplete,"user");z(o,"aoPreDrawCallback",e.fnPreDrawCallback,"user");o.rowIdFn=Q(e.rowId);gb(o);i=o.oClasses;e.bJQueryUI?(h.extend(i,m.ext.oJUIClasses,e.oClasses),e.sDom===l.sDom&&"lfrtip"===l.sDom&&(o.sDom='<"H"lfr>t<"F"ip>'),o.renderer)?h.isPlainObject(o.renderer)&&!o.renderer.header&&(o.renderer.header="jqueryui"):
o.renderer="jqueryui":h.extend(i,m.ext.classes,e.oClasses);q.addClass(i.sTable);o.iInitDisplayStart===k&&(o.iInitDisplayStart=e.iDisplayStart,o._iDisplayStart=e.iDisplayStart);null!==e.iDeferLoading&&(o.bDeferLoading=!0,g=h.isArray(e.iDeferLoading),o._iRecordsDisplay=g?e.iDeferLoading[0]:e.iDeferLoading,o._iRecordsTotal=g?e.iDeferLoading[1]:e.iDeferLoading);var r=o.oLanguage;h.extend(!0,r,e.oLanguage);""!==r.sUrl&&(h.ajax({dataType:"json",url:r.sUrl,success:function(a){Da(a);K(l.oLanguage,a);h.extend(true,
r,a);ga(o)},error:function(){ga(o)}}),n=!0);null===e.asStripeClasses&&(o.asStripeClasses=[i.sStripeOdd,i.sStripeEven]);var g=o.asStripeClasses,v=q.children("tbody").find("tr").eq(0);-1!==h.inArray(!0,h.map(g,function(a){return v.hasClass(a)}))&&(h("tbody tr",this).removeClass(g.join(" ")),o.asDestroyStripes=g.slice());t=[];g=this.getElementsByTagName("thead");0!==g.length&&(da(o.aoHeader,g[0]),t=qa(o));if(null===e.aoColumns){p=[];g=0;for(j=t.length;g<j;g++)p.push(null)}else p=e.aoColumns;g=0;for(j=
p.length;g<j;g++)Ea(o,t?t[g]:null);ib(o,e.aoColumnDefs,p,function(a,b){ja(o,a,b)});if(v.length){var s=function(a,b){return a.getAttribute("data-"+b)!==null?b:null};h(v[0]).children("th, td").each(function(a,b){var c=o.aoColumns[a];if(c.mData===a){var d=s(b,"sort")||s(b,"order"),e=s(b,"filter")||s(b,"search");if(d!==null||e!==null){c.mData={_:a+".display",sort:d!==null?a+".@data-"+d:k,type:d!==null?a+".@data-"+d:k,filter:e!==null?a+".@data-"+e:k};ja(o,a)}}})}var w=o.oFeatures;e.bStateSave&&(w.bStateSave=
!0,Kb(o,e),z(o,"aoDrawCallback",wa,"state_save"));if(e.aaSorting===k){t=o.aaSorting;g=0;for(j=t.length;g<j;g++)t[g][1]=o.aoColumns[g].asSorting[0]}va(o);w.bSort&&z(o,"aoDrawCallback",function(){if(o.bSorted){var a=V(o),b={};h.each(a,function(a,c){b[c.src]=c.dir});u(o,null,"order",[o,a,b]);Jb(o)}});z(o,"aoDrawCallback",function(){(o.bSorted||y(o)==="ssp"||w.bDeferRender)&&va(o)},"sc");g=q.children("caption").each(function(){this._captionSide=q.css("caption-side")});j=q.children("thead");0===j.length&&
(j=h("<thead/>").appendTo(this));o.nTHead=j[0];j=q.children("tbody");0===j.length&&(j=h("<tbody/>").appendTo(this));o.nTBody=j[0];j=q.children("tfoot");if(0===j.length&&0<g.length&&(""!==o.oScroll.sX||""!==o.oScroll.sY))j=h("<tfoot/>").appendTo(this);0===j.length||0===j.children().length?q.addClass(i.sNoFooter):0<j.length&&(o.nTFoot=j[0],da(o.aoFooter,o.nTFoot));if(e.aaData)for(g=0;g<e.aaData.length;g++)N(o,e.aaData[g]);else(o.bDeferLoading||"dom"==y(o))&&ma(o,h(o.nTBody).children("tr"));o.aiDisplay=
o.aiDisplayMaster.slice();o.bInitialised=!0;!1===n&&ga(o)}});b=null;return this},v,r,p,s,Ya={},Ob=/[\r\n]/g,Aa=/<.*?>/g,ac=/^[\w\+\-]/,bc=/[\w\+\-]$/,cc=RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)","g"),Xa=/[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi,M=function(a){return!a||!0===a||"-"===a?!0:!1},Pb=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},Qb=function(a,b){Ya[b]||(Ya[b]=RegExp(Qa(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g,
"").replace(Ya[b],"."):a},Za=function(a,b,c){var d="string"===typeof a;if(M(a))return!0;b&&d&&(a=Qb(a,b));c&&d&&(a=a.replace(Xa,""));return!isNaN(parseFloat(a))&&isFinite(a)},Rb=function(a,b,c){return M(a)?!0:!(M(a)||"string"===typeof a)?null:Za(a.replace(Aa,""),b,c)?!0:null},G=function(a,b,c){var d=[],e=0,f=a.length;if(c!==k)for(;e<f;e++)a[e]&&a[e][b]&&d.push(a[e][b][c]);else for(;e<f;e++)a[e]&&d.push(a[e][b]);return d},ha=function(a,b,c,d){var e=[],f=0,g=b.length;if(d!==k)for(;f<g;f++)a[b[f]][c]&&
e.push(a[b[f]][c][d]);else for(;f<g;f++)e.push(a[b[f]][c]);return e},W=function(a,b){var c=[],d;b===k?(b=0,d=a):(d=b,b=a);for(var e=b;e<d;e++)c.push(e);return c},Sb=function(a){for(var b=[],c=0,d=a.length;c<d;c++)a[c]&&b.push(a[c]);return b},pa=function(a){var b=[],c,d,e=a.length,f,g=0;d=0;a:for(;d<e;d++){c=a[d];for(f=0;f<g;f++)if(b[f]===c)continue a;b.push(c);g++}return b};m.util={throttle:function(a,b){var c=b!==k?b:200,d,e;return function(){var b=this,g=+new Date,h=arguments;d&&g<d+c?(clearTimeout(e),
e=setTimeout(function(){d=k;a.apply(b,h)},c)):(d=g,a.apply(b,h))}},escapeRegex:function(a){return a.replace(cc,"\\$1")}};var A=function(a,b,c){a[b]!==k&&(a[c]=a[b])},ba=/\[.*?\]$/,U=/\(\)$/,Qa=m.util.escapeRegex,ua=h("<div>")[0],Zb=ua.textContent!==k,$b=/<.*?>/g,Oa=m.util.throttle,Tb=[],w=Array.prototype,dc=function(a){var b,c,d=m.settings,e=h.map(d,function(a){return a.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase())return b=h.inArray(a,e),-1!==b?[d[b]]:
null;if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===typeof a?c=h(a):a instanceof h&&(c=a)}else return[];if(c)return c.map(function(){b=h.inArray(this,e);return-1!==b?d[b]:null}).toArray()};r=function(a,b){if(!(this instanceof r))return new r(a,b);var c=[],d=function(a){(a=dc(a))&&(c=c.concat(a))};if(h.isArray(a))for(var e=0,f=a.length;e<f;e++)d(a[e]);else d(a);this.context=pa(c);b&&h.merge(this,b);this.selector={rows:null,cols:null,opts:null};r.extend(this,this,Tb)};
m.Api=r;h.extend(r.prototype,{any:function(){return 0!==this.count()},concat:w.concat,context:[],count:function(){return this.flatten().length},each:function(a){for(var b=0,c=this.length;b<c;b++)a.call(this,this[b],b,this);return this},eq:function(a){var b=this.context;return b.length>a?new r(b[a],this[a]):null},filter:function(a){var b=[];if(w.filter)b=w.filter.call(this,a,this);else for(var c=0,d=this.length;c<d;c++)a.call(this,this[c],c,this)&&b.push(this[c]);return new r(this.context,b)},flatten:function(){var a=
[];return new r(this.context,a.concat.apply(a,this.toArray()))},join:w.join,indexOf:w.indexOf||function(a,b){for(var c=b||0,d=this.length;c<d;c++)if(this[c]===a)return c;return-1},iterator:function(a,b,c,d){var e=[],f,g,h,i,n,l=this.context,m,t,p=this.selector;"string"===typeof a&&(d=c,c=b,b=a,a=!1);g=0;for(h=l.length;g<h;g++){var o=new r(l[g]);if("table"===b)f=c.call(o,l[g],g),f!==k&&e.push(f);else if("columns"===b||"rows"===b)f=c.call(o,l[g],this[g],g),f!==k&&e.push(f);else if("column"===b||"column-rows"===
b||"row"===b||"cell"===b){t=this[g];"column-rows"===b&&(m=Ba(l[g],p.opts));i=0;for(n=t.length;i<n;i++)f=t[i],f="cell"===b?c.call(o,l[g],f.row,f.column,g,i):c.call(o,l[g],f,g,i,m),f!==k&&e.push(f)}}return e.length||d?(a=new r(l,a?e.concat.apply([],e):e),b=a.selector,b.rows=p.rows,b.cols=p.cols,b.opts=p.opts,a):this},lastIndexOf:w.lastIndexOf||function(a,b){return this.indexOf.apply(this.toArray.reverse(),arguments)},length:0,map:function(a){var b=[];if(w.map)b=w.map.call(this,a,this);else for(var c=
0,d=this.length;c<d;c++)b.push(a.call(this,this[c],c));return new r(this.context,b)},pluck:function(a){return this.map(function(b){return b[a]})},pop:w.pop,push:w.push,reduce:w.reduce||function(a,b){return hb(this,a,b,0,this.length,1)},reduceRight:w.reduceRight||function(a,b){return hb(this,a,b,this.length-1,-1,-1)},reverse:w.reverse,selector:null,shift:w.shift,sort:w.sort,splice:w.splice,toArray:function(){return w.slice.call(this)},to$:function(){return h(this)},toJQuery:function(){return h(this)},
unique:function(){return new r(this.context,pa(this))},unshift:w.unshift});r.extend=function(a,b,c){if(c.length&&b&&(b instanceof r||b.__dt_wrapper)){var d,e,f,g=function(a,b,c){return function(){var d=b.apply(a,arguments);r.extend(d,d,c.methodExt);return d}};d=0;for(e=c.length;d<e;d++)f=c[d],b[f.name]="function"===typeof f.val?g(a,f.val,f):h.isPlainObject(f.val)?{}:f.val,b[f.name].__dt_wrapper=!0,r.extend(a,b[f.name],f.propExt)}};r.register=p=function(a,b){if(h.isArray(a))for(var c=0,d=a.length;c<
d;c++)r.register(a[c],b);else for(var e=a.split("."),f=Tb,g,j,c=0,d=e.length;c<d;c++){g=(j=-1!==e[c].indexOf("()"))?e[c].replace("()",""):e[c];var i;a:{i=0;for(var n=f.length;i<n;i++)if(f[i].name===g){i=f[i];break a}i=null}i||(i={name:g,val:{},methodExt:[],propExt:[]},f.push(i));c===d-1?i.val=b:f=j?i.methodExt:i.propExt}};r.registerPlural=s=function(a,b,c){r.register(a,c);r.register(b,function(){var a=c.apply(this,arguments);return a===this?this:a instanceof r?a.length?h.isArray(a[0])?new r(a.context,
a[0]):a[0]:k:a})};p("tables()",function(a){var b;if(a){b=r;var c=this.context;if("number"===typeof a)a=[c[a]];else var d=h.map(c,function(a){return a.nTable}),a=h(d).filter(a).map(function(){var a=h.inArray(this,d);return c[a]}).toArray();b=new b(a)}else b=this;return b});p("table()",function(a){var a=this.tables(a),b=a.context;return b.length?new r(b[0]):a});s("tables().nodes()","table().node()",function(){return this.iterator("table",function(a){return a.nTable},1)});s("tables().body()","table().body()",
function(){return this.iterator("table",function(a){return a.nTBody},1)});s("tables().header()","table().header()",function(){return this.iterator("table",function(a){return a.nTHead},1)});s("tables().footer()","table().footer()",function(){return this.iterator("table",function(a){return a.nTFoot},1)});s("tables().containers()","table().container()",function(){return this.iterator("table",function(a){return a.nTableWrapper},1)});p("draw()",function(a){return this.iterator("table",function(b){"page"===
a?O(b):("string"===typeof a&&(a="full-hold"===a?!1:!0),T(b,!1===a))})});p("page()",function(a){return a===k?this.page.info().page:this.iterator("table",function(b){Ta(b,a)})});p("page.info()",function(){if(0===this.context.length)return k;var a=this.context[0],b=a._iDisplayStart,c=a.oFeatures.bPaginate?a._iDisplayLength:-1,d=a.fnRecordsDisplay(),e=-1===c;return{page:e?0:Math.floor(b/c),pages:e?1:Math.ceil(d/c),start:b,end:a.fnDisplayEnd(),length:c,recordsTotal:a.fnRecordsTotal(),recordsDisplay:d,
serverSide:"ssp"===y(a)}});p("page.len()",function(a){return a===k?0!==this.context.length?this.context[0]._iDisplayLength:k:this.iterator("table",function(b){Ra(b,a)})});var Ub=function(a,b,c){if(c){var d=new r(a);d.one("draw",function(){c(d.ajax.json())})}if("ssp"==y(a))T(a,b);else{C(a,!0);var e=a.jqXHR;e&&4!==e.readyState&&e.abort();ra(a,[],function(c){na(a);for(var c=sa(a,c),d=0,e=c.length;d<e;d++)N(a,c[d]);T(a,b);C(a,!1)})}};p("ajax.json()",function(){var a=this.context;if(0<a.length)return a[0].json});
p("ajax.params()",function(){var a=this.context;if(0<a.length)return a[0].oAjaxData});p("ajax.reload()",function(a,b){return this.iterator("table",function(c){Ub(c,!1===b,a)})});p("ajax.url()",function(a){var b=this.context;if(a===k){if(0===b.length)return k;b=b[0];return b.ajax?h.isPlainObject(b.ajax)?b.ajax.url:b.ajax:b.sAjaxSource}return this.iterator("table",function(b){h.isPlainObject(b.ajax)?b.ajax.url=a:b.ajax=a})});p("ajax.url().load()",function(a,b){return this.iterator("table",function(c){Ub(c,
!1===b,a)})});var $a=function(a,b,c,d,e){var f=[],g,j,i,n,l,m;i=typeof b;if(!b||"string"===i||"function"===i||b.length===k)b=[b];i=0;for(n=b.length;i<n;i++){j=b[i]&&b[i].split?b[i].split(","):[b[i]];l=0;for(m=j.length;l<m;l++)(g=c("string"===typeof j[l]?h.trim(j[l]):j[l]))&&g.length&&(f=f.concat(g))}a=v.selector[a];if(a.length){i=0;for(n=a.length;i<n;i++)f=a[i](d,e,f)}return pa(f)},ab=function(a){a||(a={});a.filter&&a.search===k&&(a.search=a.filter);return h.extend({search:"none",order:"current",
page:"all"},a)},bb=function(a){for(var b=0,c=a.length;b<c;b++)if(0<a[b].length)return a[0]=a[b],a[0].length=1,a.length=1,a.context=[a.context[b]],a;a.length=0;return a},Ba=function(a,b){var c,d,e,f=[],g=a.aiDisplay;c=a.aiDisplayMaster;var j=b.search;d=b.order;e=b.page;if("ssp"==y(a))return"removed"===j?[]:W(0,c.length);if("current"==e){c=a._iDisplayStart;for(d=a.fnDisplayEnd();c<d;c++)f.push(g[c])}else if("current"==d||"applied"==d)f="none"==j?c.slice():"applied"==j?g.slice():h.map(c,function(a){return-1===
h.inArray(a,g)?a:null});else if("index"==d||"original"==d){c=0;for(d=a.aoData.length;c<d;c++)"none"==j?f.push(c):(e=h.inArray(c,g),(-1===e&&"removed"==j||0<=e&&"applied"==j)&&f.push(c))}return f};p("rows()",function(a,b){a===k?a="":h.isPlainObject(a)&&(b=a,a="");var b=ab(b),c=this.iterator("table",function(c){var e=b;return $a("row",a,function(a){var b=Pb(a);if(b!==null&&!e)return[b];var j=Ba(c,e);if(b!==null&&h.inArray(b,j)!==-1)return[b];if(!a)return j;if(typeof a==="function")return h.map(j,function(b){var e=
c.aoData[b];return a(b,e._aData,e.nTr)?b:null});b=Sb(ha(c.aoData,j,"nTr"));if(a.nodeName){if(a._DT_RowIndex!==k)return[a._DT_RowIndex];if(a._DT_CellIndex)return[a._DT_CellIndex.row];b=h(a).closest("*[data-dt-row]");return b.length?[b.data("dt-row")]:[]}if(typeof a==="string"&&a.charAt(0)==="#"){j=c.aIds[a.replace(/^#/,"")];if(j!==k)return[j.idx]}return h(b).filter(a).map(function(){return this._DT_RowIndex}).toArray()},c,e)},1);c.selector.rows=a;c.selector.opts=b;return c});p("rows().nodes()",function(){return this.iterator("row",
function(a,b){return a.aoData[b].nTr||k},1)});p("rows().data()",function(){return this.iterator(!0,"rows",function(a,b){return ha(a.aoData,b,"_aData")},1)});s("rows().cache()","row().cache()",function(a){return this.iterator("row",function(b,c){var d=b.aoData[c];return"search"===a?d._aFilterData:d._aSortData},1)});s("rows().invalidate()","row().invalidate()",function(a){return this.iterator("row",function(b,c){ca(b,c,a)})});s("rows().indexes()","row().index()",function(){return this.iterator("row",
function(a,b){return b},1)});s("rows().ids()","row().id()",function(a){for(var b=[],c=this.context,d=0,e=c.length;d<e;d++)for(var f=0,g=this[d].length;f<g;f++){var h=c[d].rowIdFn(c[d].aoData[this[d][f]]._aData);b.push((!0===a?"#":"")+h)}return new r(c,b)});s("rows().remove()","row().remove()",function(){var a=this;this.iterator("row",function(b,c,d){var e=b.aoData,f=e[c],g,h,i,n,l;e.splice(c,1);g=0;for(h=e.length;g<h;g++)if(i=e[g],l=i.anCells,null!==i.nTr&&(i.nTr._DT_RowIndex=g),null!==l){i=0;for(n=
l.length;i<n;i++)l[i]._DT_CellIndex.row=g}oa(b.aiDisplayMaster,c);oa(b.aiDisplay,c);oa(a[d],c,!1);Sa(b);c=b.rowIdFn(f._aData);c!==k&&delete b.aIds[c]});this.iterator("table",function(a){for(var c=0,d=a.aoData.length;c<d;c++)a.aoData[c].idx=c});return this});p("rows.add()",function(a){var b=this.iterator("table",function(b){var c,f,g,h=[];f=0;for(g=a.length;f<g;f++)c=a[f],c.nodeName&&"TR"===c.nodeName.toUpperCase()?h.push(ma(b,c)[0]):h.push(N(b,c));return h},1),c=this.rows(-1);c.pop();h.merge(c,b);
return c});p("row()",function(a,b){return bb(this.rows(a,b))});p("row().data()",function(a){var b=this.context;if(a===k)return b.length&&this.length?b[0].aoData[this[0]]._aData:k;b[0].aoData[this[0]]._aData=a;ca(b[0],this[0],"data");return this});p("row().node()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]].nTr||null:null});p("row.add()",function(a){a instanceof h&&a.length&&(a=a[0]);var b=this.iterator("table",function(b){return a.nodeName&&"TR"===a.nodeName.toUpperCase()?
ma(b,a)[0]:N(b,a)});return this.row(b[0])});var cb=function(a,b){var c=a.context;if(c.length&&(c=c[0].aoData[b!==k?b:a[0]])&&c._details)c._details.remove(),c._detailsShow=k,c._details=k},Vb=function(a,b){var c=a.context;if(c.length&&a.length){var d=c[0].aoData[a[0]];if(d._details){(d._detailsShow=b)?d._details.insertAfter(d.nTr):d._details.detach();var e=c[0],f=new r(e),g=e.aoData;f.off("draw.dt.DT_details column-visibility.dt.DT_details destroy.dt.DT_details");0<G(g,"_details").length&&(f.on("draw.dt.DT_details",
function(a,b){e===b&&f.rows({page:"current"}).eq(0).each(function(a){a=g[a];a._detailsShow&&a._details.insertAfter(a.nTr)})}),f.on("column-visibility.dt.DT_details",function(a,b){if(e===b)for(var c,d=aa(b),f=0,h=g.length;f<h;f++)c=g[f],c._details&&c._details.children("td[colspan]").attr("colspan",d)}),f.on("destroy.dt.DT_details",function(a,b){if(e===b)for(var c=0,d=g.length;c<d;c++)g[c]._details&&cb(f,c)}))}}};p("row().child()",function(a,b){var c=this.context;if(a===k)return c.length&&this.length?
c[0].aoData[this[0]]._details:k;if(!0===a)this.child.show();else if(!1===a)cb(this);else if(c.length&&this.length){var d=c[0],c=c[0].aoData[this[0]],e=[],f=function(a,b){if(h.isArray(a)||a instanceof h)for(var c=0,k=a.length;c<k;c++)f(a[c],b);else a.nodeName&&"tr"===a.nodeName.toLowerCase()?e.push(a):(c=h("<tr><td/></tr>").addClass(b),h("td",c).addClass(b).html(a)[0].colSpan=aa(d),e.push(c[0]))};f(a,b);c._details&&c._details.remove();c._details=h(e);c._detailsShow&&c._details.insertAfter(c.nTr)}return this});
p(["row().child.show()","row().child().show()"],function(){Vb(this,!0);return this});p(["row().child.hide()","row().child().hide()"],function(){Vb(this,!1);return this});p(["row().child.remove()","row().child().remove()"],function(){cb(this);return this});p("row().child.isShown()",function(){var a=this.context;return a.length&&this.length?a[0].aoData[this[0]]._detailsShow||!1:!1});var ec=/^(.+):(name|visIdx|visible)$/,Wb=function(a,b,c,d,e){for(var c=[],d=0,f=e.length;d<f;d++)c.push(B(a,e[d],b));
return c};p("columns()",function(a,b){a===k?a="":h.isPlainObject(a)&&(b=a,a="");var b=ab(b),c=this.iterator("table",function(c){var e=a,f=b,g=c.aoColumns,j=G(g,"sName"),i=G(g,"nTh");return $a("column",e,function(a){var b=Pb(a);if(a==="")return W(g.length);if(b!==null)return[b>=0?b:g.length+b];if(typeof a==="function"){var e=Ba(c,f);return h.map(g,function(b,f){return a(f,Wb(c,f,0,0,e),i[f])?f:null})}var k=typeof a==="string"?a.match(ec):"";if(k)switch(k[2]){case "visIdx":case "visible":b=parseInt(k[1],
10);if(b<0){var m=h.map(g,function(a,b){return a.bVisible?b:null});return[m[m.length+b]]}return[Z(c,b)];case "name":return h.map(j,function(a,b){return a===k[1]?b:null});default:return[]}if(a.nodeName&&a._DT_CellIndex)return[a._DT_CellIndex.column];b=h(i).filter(a).map(function(){return h.inArray(this,i)}).toArray();if(b.length||!a.nodeName)return b;b=h(a).closest("*[data-dt-column]");return b.length?[b.data("dt-column")]:[]},c,f)},1);c.selector.cols=a;c.selector.opts=b;return c});s("columns().header()",
"column().header()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTh},1)});s("columns().footer()","column().footer()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTf},1)});s("columns().data()","column().data()",function(){return this.iterator("column-rows",Wb,1)});s("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].mData},1)});s("columns().cache()","column().cache()",
function(a){return this.iterator("column-rows",function(b,c,d,e,f){return ha(b.aoData,f,"search"===a?"_aFilterData":"_aSortData",c)},1)});s("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,d,e){return ha(a.aoData,e,"anCells",b)},1)});s("columns().visible()","column().visible()",function(a,b){var c=this.iterator("column",function(b,c){if(a===k)return b.aoColumns[c].bVisible;var f=b.aoColumns,g=f[c],j=b.aoData,i,n,l;if(a!==k&&g.bVisible!==a){if(a){var m=
h.inArray(!0,G(f,"bVisible"),c+1);i=0;for(n=j.length;i<n;i++)l=j[i].nTr,f=j[i].anCells,l&&l.insertBefore(f[c],f[m]||null)}else h(G(b.aoData,"anCells",c)).detach();g.bVisible=a;ea(b,b.aoHeader);ea(b,b.aoFooter);wa(b)}});a!==k&&(this.iterator("column",function(c,e){u(c,null,"column-visibility",[c,e,a,b])}),(b===k||b)&&this.columns.adjust());return c});s("columns().indexes()","column().index()",function(a){return this.iterator("column",function(b,c){return"visible"===a?$(b,c):c},1)});p("columns.adjust()",
function(){return this.iterator("table",function(a){Y(a)},1)});p("column.index()",function(a,b){if(0!==this.context.length){var c=this.context[0];if("fromVisible"===a||"toData"===a)return Z(c,b);if("fromData"===a||"toVisible"===a)return $(c,b)}});p("column()",function(a,b){return bb(this.columns(a,b))});p("cells()",function(a,b,c){h.isPlainObject(a)&&(a.row===k?(c=a,a=null):(c=b,b=null));h.isPlainObject(b)&&(c=b,b=null);if(null===b||b===k)return this.iterator("table",function(b){var d=a,e=ab(c),f=
b.aoData,g=Ba(b,e),j=Sb(ha(f,g,"anCells")),i=h([].concat.apply([],j)),l,n=b.aoColumns.length,m,p,r,u,v,s;return $a("cell",d,function(a){var c=typeof a==="function";if(a===null||a===k||c){m=[];p=0;for(r=g.length;p<r;p++){l=g[p];for(u=0;u<n;u++){v={row:l,column:u};if(c){s=f[l];a(v,B(b,l,u),s.anCells?s.anCells[u]:null)&&m.push(v)}else m.push(v)}}return m}if(h.isPlainObject(a))return[a];c=i.filter(a).map(function(a,b){return{row:b._DT_CellIndex.row,column:b._DT_CellIndex.column}}).toArray();if(c.length||
!a.nodeName)return c;s=h(a).closest("*[data-dt-row]");return s.length?[{row:s.data("dt-row"),column:s.data("dt-column")}]:[]},b,e)});var d=this.columns(b,c),e=this.rows(a,c),f,g,j,i,n,l=this.iterator("table",function(a,b){f=[];g=0;for(j=e[b].length;g<j;g++){i=0;for(n=d[b].length;i<n;i++)f.push({row:e[b][g],column:d[b][i]})}return f},1);h.extend(l.selector,{cols:b,rows:a,opts:c});return l});s("cells().nodes()","cell().node()",function(){return this.iterator("cell",function(a,b,c){return(a=a.aoData[b])&&
a.anCells?a.anCells[c]:k},1)});p("cells().data()",function(){return this.iterator("cell",function(a,b,c){return B(a,b,c)},1)});s("cells().cache()","cell().cache()",function(a){a="search"===a?"_aFilterData":"_aSortData";return this.iterator("cell",function(b,c,d){return b.aoData[c][a][d]},1)});s("cells().render()","cell().render()",function(a){return this.iterator("cell",function(b,c,d){return B(b,c,d,a)},1)});s("cells().indexes()","cell().index()",function(){return this.iterator("cell",function(a,
b,c){return{row:b,column:c,columnVisible:$(a,c)}},1)});s("cells().invalidate()","cell().invalidate()",function(a){return this.iterator("cell",function(b,c,d){ca(b,c,a,d)})});p("cell()",function(a,b,c){return bb(this.cells(a,b,c))});p("cell().data()",function(a){var b=this.context,c=this[0];if(a===k)return b.length&&c.length?B(b[0],c[0].row,c[0].column):k;jb(b[0],c[0].row,c[0].column,a);ca(b[0],c[0].row,"data",c[0].column);return this});p("order()",function(a,b){var c=this.context;if(a===k)return 0!==
c.length?c[0].aaSorting:k;"number"===typeof a?a=[[a,b]]:a.length&&!h.isArray(a[0])&&(a=Array.prototype.slice.call(arguments));return this.iterator("table",function(b){b.aaSorting=a.slice()})});p("order.listener()",function(a,b,c){return this.iterator("table",function(d){Ma(d,a,b,c)})});p("order.fixed()",function(a){if(!a){var b=this.context,b=b.length?b[0].aaSortingFixed:k;return h.isArray(b)?{pre:b}:b}return this.iterator("table",function(b){b.aaSortingFixed=h.extend(!0,{},a)})});p(["columns().order()",
"column().order()"],function(a){var b=this;return this.iterator("table",function(c,d){var e=[];h.each(b[d],function(b,c){e.push([c,a])});c.aaSorting=e})});p("search()",function(a,b,c,d){var e=this.context;return a===k?0!==e.length?e[0].oPreviousSearch.sSearch:k:this.iterator("table",function(e){e.oFeatures.bFilter&&fa(e,h.extend({},e.oPreviousSearch,{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===d?!0:d}),1)})});s("columns().search()","column().search()",function(a,
b,c,d){return this.iterator("column",function(e,f){var g=e.aoPreSearchCols;if(a===k)return g[f].sSearch;e.oFeatures.bFilter&&(h.extend(g[f],{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===d?!0:d}),fa(e,e.oPreviousSearch,1))})});p("state()",function(){return this.context.length?this.context[0].oSavedState:null});p("state.clear()",function(){return this.iterator("table",function(a){a.fnStateSaveCallback.call(a.oInstance,a,{})})});p("state.loaded()",function(){return this.context.length?
this.context[0].oLoadedState:null});p("state.save()",function(){return this.iterator("table",function(a){wa(a)})});m.versionCheck=m.fnVersionCheck=function(a){for(var b=m.version.split("."),a=a.split("."),c,d,e=0,f=a.length;e<f;e++)if(c=parseInt(b[e],10)||0,d=parseInt(a[e],10)||0,c!==d)return c>d;return!0};m.isDataTable=m.fnIsDataTable=function(a){var b=h(a).get(0),c=!1;h.each(m.settings,function(a,e){var f=e.nScrollHead?h("table",e.nScrollHead)[0]:null,g=e.nScrollFoot?h("table",e.nScrollFoot)[0]:
null;if(e.nTable===b||f===b||g===b)c=!0});return c};m.tables=m.fnTables=function(a){var b=!1;h.isPlainObject(a)&&(b=a.api,a=a.visible);var c=h.map(m.settings,function(b){if(!a||a&&h(b.nTable).is(":visible"))return b.nTable});return b?new r(c):c};m.camelToHungarian=K;p("$()",function(a,b){var c=this.rows(b).nodes(),c=h(c);return h([].concat(c.filter(a).toArray(),c.find(a).toArray()))});h.each(["on","one","off"],function(a,b){p(b+"()",function(){var a=Array.prototype.slice.call(arguments);a[0].match(/\.dt\b/)||
(a[0]+=".dt");var d=h(this.tables().nodes());d[b].apply(d,a);return this})});p("clear()",function(){return this.iterator("table",function(a){na(a)})});p("settings()",function(){return new r(this.context,this.context)});p("init()",function(){var a=this.context;return a.length?a[0].oInit:null});p("data()",function(){return this.iterator("table",function(a){return G(a.aoData,"_aData")}).flatten()});p("destroy()",function(a){a=a||!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,
d=b.oClasses,e=b.nTable,f=b.nTBody,g=b.nTHead,j=b.nTFoot,i=h(e),f=h(f),k=h(b.nTableWrapper),l=h.map(b.aoData,function(a){return a.nTr}),p;b.bDestroying=!0;u(b,"aoDestroyCallback","destroy",[b]);a||(new r(b)).columns().visible(!0);k.unbind(".DT").find(":not(tbody *)").unbind(".DT");h(D).unbind(".DT-"+b.sInstance);e!=g.parentNode&&(i.children("thead").detach(),i.append(g));j&&e!=j.parentNode&&(i.children("tfoot").detach(),i.append(j));b.aaSorting=[];b.aaSortingFixed=[];va(b);h(l).removeClass(b.asStripeClasses.join(" "));
h("th, td",g).removeClass(d.sSortable+" "+d.sSortableAsc+" "+d.sSortableDesc+" "+d.sSortableNone);b.bJUI&&(h("th span."+d.sSortIcon+", td span."+d.sSortIcon,g).detach(),h("th, td",g).each(function(){var a=h("div."+d.sSortJUIWrapper,this);h(this).append(a.contents());a.detach()}));f.children().detach();f.append(l);g=a?"remove":"detach";i[g]();k[g]();!a&&c&&(c.insertBefore(e,b.nTableReinsertBefore),i.css("width",b.sDestroyWidth).removeClass(d.sTable),(p=b.asDestroyStripes.length)&&f.children().each(function(a){h(this).addClass(b.asDestroyStripes[a%
p])}));c=h.inArray(b,m.settings);-1!==c&&m.settings.splice(c,1)})});h.each(["column","row","cell"],function(a,b){p(b+"s().every()",function(a){var d=this.selector.opts,e=this;return this.iterator(b,function(f,g,h,i,n){a.call(e[b](g,"cell"===b?h:d,"cell"===b?d:k),g,h,i,n)})})});p("i18n()",function(a,b,c){var d=this.context[0],a=Q(a)(d.oLanguage);a===k&&(a=b);c!==k&&h.isPlainObject(a)&&(a=a[c]!==k?a[c]:a._);return a.replace("%d",c)});m.version="1.10.12";m.settings=[];m.models={};m.models.oSearch={bCaseInsensitive:!0,
sSearch:"",bRegex:!1,bSmart:!0};m.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null,idx:-1};m.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",sSortingClass:null,sSortingClassJUI:null,
sTitle:null,sType:null,sWidth:null,sWidthOrig:null};m.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bJQueryUI:!1,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,
fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){}},fnStateLoadParams:null,fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===
a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}},fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",
sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:h.extend({},m.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",
renderer:null,rowId:"DT_RowId"};X(m.defaults);m.defaults.column={aDataSort:null,iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};X(m.defaults.column);m.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,
bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1,bBounding:!1,barWidth:0},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aIds:{},aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],
aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,bAjaxDataGet:!0,jqXHR:null,json:k,oAjaxData:k,
fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,bJUI:null,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==y(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==y(this)?1*this._iRecordsDisplay:this.aiDisplay.length},fnDisplayEnd:function(){var a=
this._iDisplayLength,b=this._iDisplayStart,c=b+a,d=this.aiDisplay.length,e=this.oFeatures,f=e.bPaginate;return e.bServerSide?!1===f||-1===a?b+d:Math.min(b+a,this._iRecordsDisplay):!f||c>d||-1===a?d:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{},rowIdFn:null,rowId:null};m.ext=v={buttons:{},classes:{},builder:"-source-",errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},
header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:m.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:m.version};h.extend(v,{afnFiltering:v.search,aTypes:v.type.detect,ofnSearch:v.type.search,oSort:v.type.order,afnSortData:v.order,aoFeatures:v.feature,oApi:v.internal,oStdClasses:v.classes,oPagination:v.pager});h.extend(m.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",
sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",
sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var Ca="",Ca="",H=Ca+"ui-state-default",ia=Ca+"css_right ui-icon ui-icon-",Xb=Ca+"fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix";h.extend(m.ext.oJUIClasses,
m.ext.classes,{sPageButton:"fg-button ui-button "+H,sPageButtonActive:"ui-state-disabled",sPageButtonDisabled:"ui-state-disabled",sPaging:"dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_",sSortAsc:H+" sorting_asc",sSortDesc:H+" sorting_desc",sSortable:H+" sorting",sSortableAsc:H+" sorting_asc_disabled",sSortableDesc:H+" sorting_desc_disabled",sSortableNone:H+" sorting_disabled",sSortJUIAsc:ia+"triangle-1-n",sSortJUIDesc:ia+"triangle-1-s",sSortJUI:ia+"carat-2-n-s",
sSortJUIAscAllowed:ia+"carat-1-n",sSortJUIDescAllowed:ia+"carat-1-s",sSortJUIWrapper:"DataTables_sort_wrapper",sSortIcon:"DataTables_sort_icon",sScrollHead:"dataTables_scrollHead "+H,sScrollFoot:"dataTables_scrollFoot "+H,sHeaderTH:H,sFooterTH:H,sJUIHeader:Xb+" ui-corner-tl ui-corner-tr",sJUIFooter:Xb+" ui-corner-bl ui-corner-br"});var Mb=m.ext.pager;h.extend(Mb,{simple:function(){return["previous","next"]},full:function(){return["first","previous","next","last"]},numbers:function(a,b){return[ya(a,
b)]},simple_numbers:function(a,b){return["previous",ya(a,b),"next"]},full_numbers:function(a,b){return["first","previous",ya(a,b),"next","last"]},_numbers:ya,numbers_length:7});h.extend(!0,m.ext.renderer,{pageButton:{_:function(a,b,c,d,e,f){var g=a.oClasses,j=a.oLanguage.oPaginate,i=a.oLanguage.oAria.paginate||{},k,l,m=0,p=function(b,d){var o,r,u,s,v=function(b){Ta(a,b.data.action,true)};o=0;for(r=d.length;o<r;o++){s=d[o];if(h.isArray(s)){u=h("<"+(s.DT_el||"div")+"/>").appendTo(b);p(u,s)}else{k=null;
l="";switch(s){case "ellipsis":b.append('<span class="ellipsis">&#x2026;</span>');break;case "first":k=j.sFirst;l=s+(e>0?"":" "+g.sPageButtonDisabled);break;case "previous":k=j.sPrevious;l=s+(e>0?"":" "+g.sPageButtonDisabled);break;case "next":k=j.sNext;l=s+(e<f-1?"":" "+g.sPageButtonDisabled);break;case "last":k=j.sLast;l=s+(e<f-1?"":" "+g.sPageButtonDisabled);break;default:k=s+1;l=e===s?g.sPageButtonActive:""}if(k!==null){u=h("<a>",{"class":g.sPageButton+" "+l,"aria-controls":a.sTableId,"aria-label":i[s],
"data-dt-idx":m,tabindex:a.iTabIndex,id:c===0&&typeof s==="string"?a.sTableId+"_"+s:null}).html(k).appendTo(b);Wa(u,{action:s},v);m++}}}},r;try{r=h(b).find(I.activeElement).data("dt-idx")}catch(o){}p(h(b).empty(),d);r&&h(b).find("[data-dt-idx="+r+"]").focus()}}});h.extend(m.ext.type.detect,[function(a,b){var c=b.oLanguage.sDecimal;return Za(a,c)?"num"+c:null},function(a){if(a&&!(a instanceof Date)&&(!ac.test(a)||!bc.test(a)))return null;var b=Date.parse(a);return null!==b&&!isNaN(b)||M(a)?"date":
null},function(a,b){var c=b.oLanguage.sDecimal;return Za(a,c,!0)?"num-fmt"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c)?"html-num"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c,!0)?"html-num-fmt"+c:null},function(a){return M(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);h.extend(m.ext.type.search,{html:function(a){return M(a)?a:"string"===typeof a?a.replace(Ob," ").replace(Aa,""):""},string:function(a){return M(a)?a:"string"===typeof a?a.replace(Ob,
" "):a}});var za=function(a,b,c,d){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=Qb(a,b));a.replace&&(c&&(a=a.replace(c,"")),d&&(a=a.replace(d,"")));return 1*a};h.extend(v.type.order,{"date-pre":function(a){return Date.parse(a)||0},"html-pre":function(a){return M(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return M(a)?"":"string"===typeof a?a.toLowerCase():!a.toString?"":a.toString()},"string-asc":function(a,b){return a<b?-1:a>b?1:0},"string-desc":function(a,
b){return a<b?1:a>b?-1:0}});db("");h.extend(!0,m.ext.renderer,{header:{_:function(a,b,c,d){h(a.nTable).on("order.dt.DT",function(e,f,g,h){if(a===f){e=c.idx;b.removeClass(c.sSortingClass+" "+d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass)}})},jqueryui:function(a,b,c,d){h("<div/>").addClass(d.sSortJUIWrapper).append(b.contents()).append(h("<span/>").addClass(d.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);h(a.nTable).on("order.dt.DT",function(e,
f,g,h){if(a===f){e=c.idx;b.removeClass(d.sSortAsc+" "+d.sSortDesc).addClass(h[e]=="asc"?d.sSortAsc:h[e]=="desc"?d.sSortDesc:c.sSortingClass);b.find("span."+d.sSortIcon).removeClass(d.sSortJUIAsc+" "+d.sSortJUIDesc+" "+d.sSortJUI+" "+d.sSortJUIAscAllowed+" "+d.sSortJUIDescAllowed).addClass(h[e]=="asc"?d.sSortJUIAsc:h[e]=="desc"?d.sSortJUIDesc:c.sSortingClassJUI)}})}}});var Yb=function(a){return"string"===typeof a?a.replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;"):a};m.render={number:function(a,
b,c,d,e){return{display:function(f){if("number"!==typeof f&&"string"!==typeof f)return f;var g=0>f?"-":"",h=parseFloat(f);if(isNaN(h))return Yb(f);f=Math.abs(h);h=parseInt(f,10);f=c?b+(f-h).toFixed(c).substring(2):"";return g+(d||"")+h.toString().replace(/\B(?=(\d{3})+(?!\d))/g,a)+f+(e||"")}}},text:function(){return{display:Yb}}};h.extend(m.ext.internal,{_fnExternApiFunc:Nb,_fnBuildAjax:ra,_fnAjaxUpdate:lb,_fnAjaxParameters:ub,_fnAjaxUpdateDraw:vb,_fnAjaxDataSrc:sa,_fnAddColumn:Ea,_fnColumnOptions:ja,
_fnAdjustColumnSizing:Y,_fnVisibleToColumnIndex:Z,_fnColumnIndexToVisible:$,_fnVisbleColumns:aa,_fnGetColumns:la,_fnColumnTypes:Ga,_fnApplyColumnDefs:ib,_fnHungarianMap:X,_fnCamelToHungarian:K,_fnLanguageCompat:Da,_fnBrowserDetect:gb,_fnAddData:N,_fnAddTr:ma,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==k?b._DT_RowIndex:null},_fnNodeToColumnIndex:function(a,b,c){return h.inArray(c,a.aoData[b].anCells)},_fnGetCellData:B,_fnSetCellData:jb,_fnSplitObjNotation:Ja,_fnGetObjectDataFn:Q,_fnSetObjectDataFn:R,
_fnGetDataMaster:Ka,_fnClearTable:na,_fnDeleteIndex:oa,_fnInvalidate:ca,_fnGetRowElements:Ia,_fnCreateTr:Ha,_fnBuildHead:kb,_fnDrawHead:ea,_fnDraw:O,_fnReDraw:T,_fnAddOptionsHtml:nb,_fnDetectHeader:da,_fnGetUniqueThs:qa,_fnFeatureHtmlFilter:pb,_fnFilterComplete:fa,_fnFilterCustom:yb,_fnFilterColumn:xb,_fnFilter:wb,_fnFilterCreateSearch:Pa,_fnEscapeRegex:Qa,_fnFilterData:zb,_fnFeatureHtmlInfo:sb,_fnUpdateInfo:Cb,_fnInfoMacros:Db,_fnInitialise:ga,_fnInitComplete:ta,_fnLengthChange:Ra,_fnFeatureHtmlLength:ob,
_fnFeatureHtmlPaginate:tb,_fnPageChange:Ta,_fnFeatureHtmlProcessing:qb,_fnProcessingDisplay:C,_fnFeatureHtmlTable:rb,_fnScrollDraw:ka,_fnApplyToChildren:J,_fnCalculateColumnWidths:Fa,_fnThrottle:Oa,_fnConvertToWidth:Fb,_fnGetWidestNode:Gb,_fnGetMaxLenString:Hb,_fnStringToCss:x,_fnSortFlatten:V,_fnSort:mb,_fnSortAria:Jb,_fnSortListener:Va,_fnSortAttachListener:Ma,_fnSortingClasses:va,_fnSortData:Ib,_fnSaveState:wa,_fnLoadState:Kb,_fnSettingsFromNode:xa,_fnLog:L,_fnMap:E,_fnBindAction:Wa,_fnCallbackReg:z,
_fnCallbackFire:u,_fnLengthOverflow:Sa,_fnRenderer:Na,_fnDataSource:y,_fnRowAttributes:La,_fnCalculateEnd:function(){}});h.fn.dataTable=m;m.$=h;h.fn.dataTableSettings=m.settings;h.fn.dataTableExt=m.ext;h.fn.DataTable=function(a){return h(this).dataTable(a).api()};h.each(m,function(a,b){h.fn.DataTable[a]=b});return h.fn.dataTable});
;
/*!
 Responsive 2.1.0
 2014-2016 SpryMedia Ltd - datatables.net/license
*/
(function(c){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(l){return c(l,window,document)}):"object"===typeof exports?module.exports=function(l,k){l||(l=window);if(!k||!k.fn.dataTable)k=require("datatables.net")(l,k).$;return c(k,l,l.document)}:c(jQuery,window,document)})(function(c,l,k,p){var m=c.fn.dataTable,j=function(a,b){if(!m.versionCheck||!m.versionCheck("1.10.3"))throw"DataTables Responsive requires DataTables 1.10.3 or newer";this.s={dt:new m.Api(a),columns:[],
current:[]};this.s.dt.settings()[0].responsive||(b&&"string"===typeof b.details?b.details={type:b.details}:b&&!1===b.details?b.details={type:!1}:b&&!0===b.details&&(b.details={type:"inline"}),this.c=c.extend(!0,{},j.defaults,m.defaults.responsive,b),a.responsive=this,this._constructor())};c.extend(j.prototype,{_constructor:function(){var a=this,b=this.s.dt,d=b.settings()[0],e=c(l).width();b.settings()[0]._responsive=this;c(l).on("resize.dtr orientationchange.dtr",m.util.throttle(function(){var b=
c(l).width();b!==e&&(a._resize(),e=b)}));d.oApi._fnCallbackReg(d,"aoRowCreatedCallback",function(e){-1!==c.inArray(!1,a.s.current)&&c("td, th",e).each(function(e){e=b.column.index("toData",e);!1===a.s.current[e]&&c(this).css("display","none")})});b.on("destroy.dtr",function(){b.off(".dtr");c(b.table().body()).off(".dtr");c(l).off("resize.dtr orientationchange.dtr");c.each(a.s.current,function(b,e){!1===e&&a._setColumnVis(b,!0)})});this.c.breakpoints.sort(function(a,b){return a.width<b.width?1:a.width>
b.width?-1:0});this._classLogic();this._resizeAuto();d=this.c.details;!1!==d.type&&(a._detailsInit(),b.on("column-visibility.dtr",function(){a._classLogic();a._resizeAuto();a._resize()}),b.on("draw.dtr",function(){a._redrawChildren()}),c(b.table().node()).addClass("dtr-"+d.type));b.on("column-reorder.dtr",function(){a._classLogic();a._resizeAuto();a._resize()});b.on("column-sizing.dtr",function(){a._resizeAuto();a._resize()});b.on("init.dtr",function(){a._resizeAuto();a._resize();c.inArray(false,
a.s.current)&&b.columns.adjust()});this._resize()},_columnsVisiblity:function(a){var b=this.s.dt,d=this.s.columns,e,f,g=d.map(function(a,b){return{columnIdx:b,priority:a.priority}}).sort(function(a,b){return a.priority!==b.priority?a.priority-b.priority:a.columnIdx-b.columnIdx}),h=c.map(d,function(b){return b.auto&&null===b.minWidth?!1:!0===b.auto?"-":-1!==c.inArray(a,b.includeIn)}),n=0;e=0;for(f=h.length;e<f;e++)!0===h[e]&&(n+=d[e].minWidth);e=b.settings()[0].oScroll;e=e.sY||e.sX?e.iBarWidth:0;b=
b.table().container().offsetWidth-e-n;e=0;for(f=h.length;e<f;e++)d[e].control&&(b-=d[e].minWidth);n=!1;e=0;for(f=g.length;e<f;e++){var i=g[e].columnIdx;"-"===h[i]&&(!d[i].control&&d[i].minWidth)&&(n||0>b-d[i].minWidth?(n=!0,h[i]=!1):h[i]=!0,b-=d[i].minWidth)}g=!1;e=0;for(f=d.length;e<f;e++)if(!d[e].control&&!d[e].never&&!h[e]){g=!0;break}e=0;for(f=d.length;e<f;e++)d[e].control&&(h[e]=g);-1===c.inArray(!0,h)&&(h[0]=!0);return h},_classLogic:function(){var a=this,b=this.c.breakpoints,d=this.s.dt,e=
d.columns().eq(0).map(function(a){var b=this.column(a),e=b.header().className,a=d.settings()[0].aoColumns[a].responsivePriority;a===p&&(b=c(b.header()).data("priority"),a=b!==p?1*b:1E4);return{className:e,includeIn:[],auto:!1,control:!1,never:e.match(/\bnever\b/)?!0:!1,priority:a}}),f=function(a,b){var d=e[a].includeIn;-1===c.inArray(b,d)&&d.push(b)},g=function(c,d,i,g){if(i)if("max-"===i){g=a._find(d).width;d=0;for(i=b.length;d<i;d++)b[d].width<=g&&f(c,b[d].name)}else if("min-"===i){g=a._find(d).width;
d=0;for(i=b.length;d<i;d++)b[d].width>=g&&f(c,b[d].name)}else{if("not-"===i){d=0;for(i=b.length;d<i;d++)-1===b[d].name.indexOf(g)&&f(c,b[d].name)}}else e[c].includeIn.push(d)};e.each(function(a,e){for(var d=a.className.split(" "),f=!1,j=0,l=d.length;j<l;j++){var k=c.trim(d[j]);if("all"===k){f=!0;a.includeIn=c.map(b,function(a){return a.name});return}if("none"===k||a.never){f=!0;return}if("control"===k){f=!0;a.control=!0;return}c.each(b,function(a,b){var d=b.name.split("-"),c=k.match(RegExp("(min\\-|max\\-|not\\-)?("+
d[0]+")(\\-[_a-zA-Z0-9])?"));c&&(f=!0,c[2]===d[0]&&c[3]==="-"+d[1]?g(e,b.name,c[1],c[2]+c[3]):c[2]===d[0]&&!c[3]&&g(e,b.name,c[1],c[2]))})}f||(a.auto=!0)});this.s.columns=e},_detailsDisplay:function(a,b){var d=this,e=this.s.dt,f=this.c.details;if(f&&!1!==f.type){var g=f.display(a,b,function(){return f.renderer(e,a[0],d._detailsObj(a[0]))});(!0===g||!1===g)&&c(e.table().node()).triggerHandler("responsive-display.dt",[e,a,g,b])}},_detailsInit:function(){var a=this,b=this.s.dt,d=this.c.details;"inline"===
d.type&&(d.target="td:first-child, th:first-child");b.on("draw.dtr",function(){a._tabIndexes()});a._tabIndexes();c(b.table().body()).on("keyup.dtr","td, th",function(a){a.keyCode===13&&c(this).data("dtr-keyboard")&&c(this).click()});var e=d.target;c(b.table().body()).on("click.dtr mousedown.dtr mouseup.dtr","string"===typeof e?e:"td, th",function(d){if(c(b.table().node()).hasClass("collapsed")&&b.row(c(this).closest("tr")).length){if(typeof e==="number"){var g=e<0?b.columns().eq(0).length+e:e;if(b.cell(this).index().column!==
g)return}g=b.row(c(this).closest("tr"));d.type==="click"?a._detailsDisplay(g,false):d.type==="mousedown"?c(this).css("outline","none"):d.type==="mouseup"&&c(this).blur().css("outline","")}})},_detailsObj:function(a){var b=this,d=this.s.dt;return c.map(this.s.columns,function(e,c){if(!e.never&&!e.control)return{title:d.settings()[0].aoColumns[c].sTitle,data:d.cell(a,c).render(b.c.orthogonal),hidden:d.column(c).visible()&&!b.s.current[c],columnIndex:c,rowIndex:a}})},_find:function(a){for(var b=this.c.breakpoints,
d=0,c=b.length;d<c;d++)if(b[d].name===a)return b[d]},_redrawChildren:function(){var a=this,b=this.s.dt;b.rows({page:"current"}).iterator("row",function(c,e){b.row(e);a._detailsDisplay(b.row(e),!0)})},_resize:function(){var a=this,b=this.s.dt,d=c(l).width(),e=this.c.breakpoints,f=e[0].name,g=this.s.columns,h,j=this.s.current.slice();for(h=e.length-1;0<=h;h--)if(d<=e[h].width){f=e[h].name;break}var i=this._columnsVisiblity(f);this.s.current=i;e=!1;h=0;for(d=g.length;h<d;h++)if(!1===i[h]&&!g[h].never&&
!g[h].control){e=!0;break}c(b.table().node()).toggleClass("collapsed",e);var k=!1;b.columns().eq(0).each(function(b,c){i[c]!==j[c]&&(k=!0,a._setColumnVis(b,i[c]))});k&&(this._redrawChildren(),c(b.table().node()).trigger("responsive-resize.dt",[b,this.s.current]))},_resizeAuto:function(){var a=this.s.dt,b=this.s.columns;if(this.c.auto&&-1!==c.inArray(!0,c.map(b,function(a){return a.auto}))){a.table().node();var d=a.table().node().cloneNode(!1),e=c(a.table().header().cloneNode(!1)).appendTo(d),f=c(a.table().body()).clone(!1,
!1).empty().appendTo(d),g=a.columns().header().filter(function(b){return a.column(b).visible()}).to$().clone(!1).css("display","table-cell");c(f).append(c(a.rows({page:"current"}).nodes()).clone(!1)).find("th, td").css("display","");if(f=a.table().footer()){var f=c(f.cloneNode(!1)).appendTo(d),h=a.columns().footer().filter(function(b){return a.column(b).visible()}).to$().clone(!1).css("display","table-cell");c("<tr/>").append(h).appendTo(f)}c("<tr/>").append(g).appendTo(e);"inline"===this.c.details.type&&
c(d).addClass("dtr-inline collapsed");c(d).find("[name]").removeAttr("name");d=c("<div/>").css({width:1,height:1,overflow:"hidden"}).append(d);d.insertBefore(a.table().node());g.each(function(c){c=a.column.index("fromVisible",c);b[c].minWidth=this.offsetWidth||0});d.remove()}},_setColumnVis:function(a,b){var d=this.s.dt,e=b?"":"none";c(d.column(a).header()).css("display",e);c(d.column(a).footer()).css("display",e);d.column(a).nodes().to$().css("display",e)},_tabIndexes:function(){var a=this.s.dt,
b=a.cells({page:"current"}).nodes().to$(),d=a.settings()[0],e=this.c.details.target;b.filter("[data-dtr-keyboard]").removeData("[data-dtr-keyboard]");c("number"===typeof e?":eq("+e+")":e,a.rows({page:"current"}).nodes()).attr("tabIndex",d.iTabIndex).data("dtr-keyboard",1)}});j.breakpoints=[{name:"desktop",width:Infinity},{name:"tablet-l",width:1024},{name:"tablet-p",width:768},{name:"mobile-l",width:480},{name:"mobile-p",width:320}];j.display={childRow:function(a,b,d){if(b){if(c(a.node()).hasClass("parent"))return a.child(d(),
"child").show(),!0}else{if(a.child.isShown())return a.child(!1),c(a.node()).removeClass("parent"),!1;a.child(d(),"child").show();c(a.node()).addClass("parent");return!0}},childRowImmediate:function(a,b,d){if(!b&&a.child.isShown()||!a.responsive.hasHidden())return a.child(!1),c(a.node()).removeClass("parent"),!1;a.child(d(),"child").show();c(a.node()).addClass("parent");return!0},modal:function(a){return function(b,d,e){if(d)c("div.dtr-modal-content").empty().append(e());else{var f=function(){g.remove();
c(k).off("keypress.dtr")},g=c('<div class="dtr-modal"/>').append(c('<div class="dtr-modal-display"/>').append(c('<div class="dtr-modal-content"/>').append(e())).append(c('<div class="dtr-modal-close">&times;</div>').click(function(){f()}))).append(c('<div class="dtr-modal-background"/>').click(function(){f()})).appendTo("body");c(k).on("keyup.dtr",function(a){27===a.keyCode&&(a.stopPropagation(),f())})}a&&a.header&&c("div.dtr-modal-content").prepend("<h2>"+a.header(b)+"</h2>")}}};j.renderer={listHidden:function(){return function(a,
b,d){return(a=c.map(d,function(a){return a.hidden?'<li data-dtr-index="'+a.columnIndex+'" data-dt-row="'+a.rowIndex+'" data-dt-column="'+a.columnIndex+'"><span class="dtr-title">'+a.title+'</span> <span class="dtr-data">'+a.data+"</span></li>":""}).join(""))?c('<ul data-dtr-index="'+b+'"/>').append(a):!1}},tableAll:function(a){a=c.extend({tableClass:""},a);return function(b,d,e){b=c.map(e,function(a){return'<tr data-dt-row="'+a.rowIndex+'" data-dt-column="'+a.columnIndex+'"><td>'+a.title+":</td> <td>"+
a.data+"</td></tr>"}).join("");return c('<table class="'+a.tableClass+'" width="100%"/>').append(b)}}};j.defaults={breakpoints:j.breakpoints,auto:!0,details:{display:j.display.childRow,renderer:j.renderer.listHidden(),target:0,type:"inline"},orthogonal:"display"};var o=c.fn.dataTable.Api;o.register("responsive()",function(){return this});o.register("responsive.index()",function(a){a=c(a);return{column:a.data("dtr-index"),row:a.parent().data("dtr-index")}});o.register("responsive.rebuild()",function(){return this.iterator("table",
function(a){a._responsive&&a._responsive._classLogic()})});o.register("responsive.recalc()",function(){return this.iterator("table",function(a){a._responsive&&(a._responsive._resizeAuto(),a._responsive._resize())})});o.register("responsive.hasHidden()",function(){var a=this.context[0];return a._responsive?-1!==c.inArray(!1,a._responsive.s.current):!1});j.version="2.1.0";c.fn.dataTable.Responsive=j;c.fn.DataTable.Responsive=j;c(k).on("preInit.dt.dtr",function(a,b){if("dt"===a.namespace&&(c(b.nTable).hasClass("responsive")||
c(b.nTable).hasClass("dt-responsive")||b.oInit.responsive||m.defaults.responsive)){var d=b.oInit.responsive;!1!==d&&new j(b,c.isPlainObject(d)?d:{})}});return j});
;
/*!
 FixedHeader 3.1.2
 ©2009-2016 SpryMedia Ltd - datatables.net/license
*/
(function(d){"function"===typeof define&&define.amd?define(["jquery","datatables.net"],function(g){return d(g,window,document)}):"object"===typeof exports?module.exports=function(g,h){g||(g=window);if(!h||!h.fn.dataTable)h=require("datatables.net")(g,h).$;return d(h,g,g.document)}:d(jQuery,window,document)})(function(d,g,h,k){var j=d.fn.dataTable,l=0,i=function(b,a){if(!(this instanceof i))throw"FixedHeader must be initialised with the 'new' keyword.";!0===a&&(a={});b=new j.Api(b);this.c=d.extend(!0,
{},i.defaults,a);this.s={dt:b,position:{theadTop:0,tbodyTop:0,tfootTop:0,tfootBottom:0,width:0,left:0,tfootHeight:0,theadHeight:0,windowHeight:d(g).height(),visible:!0},headerMode:null,footerMode:null,autoWidth:b.settings()[0].oFeatures.bAutoWidth,namespace:".dtfc"+l++,scrollLeft:{header:-1,footer:-1},enable:!0};this.dom={floatingHeader:null,thead:d(b.table().header()),tbody:d(b.table().body()),tfoot:d(b.table().footer()),header:{host:null,floating:null,placeholder:null},footer:{host:null,floating:null,
placeholder:null}};this.dom.header.host=this.dom.thead.parent();this.dom.footer.host=this.dom.tfoot.parent();var e=b.settings()[0];if(e._fixedHeader)throw"FixedHeader already initialised on table "+e.nTable.id;e._fixedHeader=this;this._constructor()};d.extend(i.prototype,{enable:function(b){this.s.enable=b;this.c.header&&this._modeChange("in-place","header",!0);this.c.footer&&this.dom.tfoot.length&&this._modeChange("in-place","footer",!0);this.update()},headerOffset:function(b){b!==k&&(this.c.headerOffset=
b,this.update());return this.c.headerOffset},footerOffset:function(b){b!==k&&(this.c.footerOffset=b,this.update());return this.c.footerOffset},update:function(){this._positions();this._scroll(!0)},_constructor:function(){var b=this,a=this.s.dt;d(g).on("scroll"+this.s.namespace,function(){b._scroll()}).on("resize"+this.s.namespace,function(){b.s.position.windowHeight=d(g).height();b.update()});var e=d(".fh-fixedHeader");!this.c.headerOffset&&e.length&&(this.c.headerOffset=e.outerHeight());e=d(".fh-fixedFooter");
!this.c.footerOffset&&e.length&&(this.c.footerOffset=e.outerHeight());a.on("column-reorder.dt.dtfc column-visibility.dt.dtfc draw.dt.dtfc column-sizing.dt.dtfc",function(){b.update()});a.on("destroy.dtfc",function(){a.off(".dtfc");d(g).off(b.s.namespace)});this._positions();this._scroll()},_clone:function(b,a){var e=this.s.dt,c=this.dom[b],f="header"===b?this.dom.thead:this.dom.tfoot;!a&&c.floating?c.floating.removeClass("fixedHeader-floating fixedHeader-locked"):(c.floating&&(c.placeholder.remove(),
this._unsize(b),c.floating.children().detach(),c.floating.remove()),c.floating=d(e.table().node().cloneNode(!1)).css("table-layout","fixed").removeAttr("id").append(f).appendTo("body"),c.placeholder=f.clone(!1),c.host.prepend(c.placeholder),this._matchWidths(c.placeholder,c.floating))},_matchWidths:function(b,a){var e=function(a){return d(a,b).map(function(){return d(this).width()}).toArray()},c=function(b,c){d(b,a).each(function(a){d(this).css({width:c[a],minWidth:c[a]})})},f=e("th"),e=e("td");c("th",
f);c("td",e)},_unsize:function(b){var a=this.dom[b].floating;a&&("footer"===b||"header"===b&&!this.s.autoWidth)?d("th, td",a).css({width:"",minWidth:""}):a&&"header"===b&&d("th, td",a).css("min-width","")},_horizontal:function(b,a){var e=this.dom[b],c=this.s.position,d=this.s.scrollLeft;e.floating&&d[b]!==a&&(e.floating.css("left",c.left-a),d[b]=a)},_modeChange:function(b,a,e){var c=this.dom[a],f=this.s.position,g=d.contains(this.dom["footer"===a?"tfoot":"thead"][0],h.activeElement)?h.activeElement:
null;if("in-place"===b){if(c.placeholder&&(c.placeholder.remove(),c.placeholder=null),this._unsize(a),"header"===a?c.host.prepend(this.dom.thead):c.host.append(this.dom.tfoot),c.floating)c.floating.remove(),c.floating=null}else"in"===b?(this._clone(a,e),c.floating.addClass("fixedHeader-floating").css("header"===a?"top":"bottom",this.c[a+"Offset"]).css("left",f.left+"px").css("width",f.width+"px"),"footer"===a&&c.floating.css("top","")):"below"===b?(this._clone(a,e),c.floating.addClass("fixedHeader-locked").css("top",
f.tfootTop-f.theadHeight).css("left",f.left+"px").css("width",f.width+"px")):"above"===b&&(this._clone(a,e),c.floating.addClass("fixedHeader-locked").css("top",f.tbodyTop).css("left",f.left+"px").css("width",f.width+"px"));g&&g!==h.activeElement&&g.focus();this.s.scrollLeft.header=-1;this.s.scrollLeft.footer=-1;this.s[a+"Mode"]=b},_positions:function(){var b=this.s.dt.table(),a=this.s.position,e=this.dom,b=d(b.node()),c=b.children("thead"),f=b.children("tfoot"),e=e.tbody;a.visible=b.is(":visible");
a.width=b.outerWidth();a.left=b.offset().left;a.theadTop=c.offset().top;a.tbodyTop=e.offset().top;a.theadHeight=a.tbodyTop-a.theadTop;f.length?(a.tfootTop=f.offset().top,a.tfootBottom=a.tfootTop+f.outerHeight(),a.tfootHeight=a.tfootBottom-a.tfootTop):(a.tfootTop=a.tbodyTop+e.outerHeight(),a.tfootBottom=a.tfootTop,a.tfootHeight=a.tfootTop)},_scroll:function(b){var a=d(h).scrollTop(),e=d(h).scrollLeft(),c=this.s.position,f;if(this.s.enable&&(this.c.header&&(f=!c.visible||a<=c.theadTop-this.c.headerOffset?
"in-place":a<=c.tfootTop-c.theadHeight-this.c.headerOffset?"in":"below",(b||f!==this.s.headerMode)&&this._modeChange(f,"header",b),this._horizontal("header",e)),this.c.footer&&this.dom.tfoot.length))a=!c.visible||a+c.windowHeight>=c.tfootBottom+this.c.footerOffset?"in-place":c.windowHeight+a>c.tbodyTop+c.tfootHeight+this.c.footerOffset?"in":"above",(b||a!==this.s.footerMode)&&this._modeChange(a,"footer",b),this._horizontal("footer",e)}});i.version="3.1.2";i.defaults={header:!0,footer:!1,headerOffset:0,
footerOffset:0};d.fn.dataTable.FixedHeader=i;d.fn.DataTable.FixedHeader=i;d(h).on("init.dt.dtfh",function(b,a){if("dt"===b.namespace){var e=a.oInit.fixedHeader,c=j.defaults.fixedHeader;if((e||c)&&!a._fixedHeader)c=d.extend({},c,e),!1!==e&&new i(a,c)}});j.Api.register("fixedHeader()",function(){});j.Api.register("fixedHeader.adjust()",function(){return this.iterator("table",function(b){(b=b._fixedHeader)&&b.update()})});j.Api.register("fixedHeader.enable()",function(b){return this.iterator("table",
function(a){(a=a._fixedHeader)&&a.enable(b!==k?b:!0)})});j.Api.register("fixedHeader.disable()",function(){return this.iterator("table",function(b){(b=b._fixedHeader)&&b.enable(!1)})});d.each(["header","footer"],function(b,a){j.Api.register("fixedHeader."+a+"Offset()",function(b){var c=this.context;return b===k?c.length&&c[0]._fixedHeader?c[0]._fixedHeader[a+"Offset"]():k:this.iterator("table",function(c){if(c=c._fixedHeader)c[a+"Offset"](b)})})});return i});
;
(function (ePlus, $, undefined) {
    ePlus.enums = {};
    ePlus.localization = {};
    ePlus.modules = {};
    ePlus.resources = {},
    ePlus.ui = {};
    ePlus.util = {};
})(window.ePlus = window.ePlus || {}, jQuery, _);;
(function (u, $, _, undefined) {
    var res = window.ePlus.resources;
    var preferences = {};
    var userPrefType = {};
    var userPrefName = {};

    function initializePreferences(prefs) {
        preferences = prefs;
    }

    function getPreference(type, name, value) {
        return _.get(preferences, [type, name].join('.'), value);
    }

    function setPreference(type, name, value) {
        return _.set(preferences, [type, name].join('.'), value || null);
    }

    function getFontColorPreference() {
        var pref = getPreference('fonts', 'fontOption');

        switch (pref) {
            case '10':
                return '#969ea4';
            case '1':
                return '#666666';
            default:
                return "#000000";
        }
    }

    function logout() {
        window.logOut();
    }

    function deleteUser(appUserId, doLogout, callback) {
        $.ajax({
            type: "DELETE",
            url: "api/users/" + appUserId
        })
            .done(function () {
                if (doLogout) {
                    window.ePlus.user.logout();
                }

                if (typeof(callback) === 'function') {
                    callback();
                }
            })
            .fail(function() {
                alert(res.getRes("error_unexpected"));
            });
    }

    function grantUserGroupAccess(appUserId, groupId) {
        if (appUserId && groupId) {
            $.ajax({
                type: "POST",
                url: "api/users/" + appUserId + "/groups/" + groupId,
                contentType: "application/json"
            })
                .done(function () {
                    if (typeof callback === "function") {
                        callback();
                    }
                })
                .fail(function () {
                    var message = res.getRes('error_unexpected');
                    alert(message);
                });
        } else {
            alert("Cannot grant group access. Invalid orgId and/or groupId.");
        }
    }

    function removeUserGroupAccess(appUserId, groupId) {
        if (appUserId && groupId) {
            $.ajax({
                type: "DELETE",
                url: "api/users/" + appUserId + "/groups/" + groupId,
                contentType: "application/json"
            })
                .done(function () {
                    if (typeof callback === "function") {
                        callback();
                    }
                })
                .fail(function () {
                    var message = res.getRes('error_unexpected');
                    alert(message);
                });
        } else {
            alert("Cannot remove group access. Invalid orgId and/or groupId.");
        }
    }

    var toggleUserGroupAccess = function (doGrantAccess, appUserId, groupId) {
        doGrantAccess ? grantUserGroupAccess(appUserId, groupId) : removeUserGroupAccess(appUserId, groupId);
    }

    u.initializePreferences = initializePreferences;
    u.getPreference = getPreference;
    u.setPreference = setPreference;
    u.getFontColorPreference = getFontColorPreference;
    u.logout = logout;
    u.deleteUser = deleteUser;
    u.userPrefType = userPrefType;
    u.userPrefName = userPrefName;
    u.toggleUserGroupAccess = toggleUserGroupAccess;
})(window.ePlus.user = window.ePlus.user || {}, jQuery, _);;
; window.ePlus.modules.org = (function () {
    var res = window.ePlus.resources;

    var grantOrgGroupAccess = function (orgId, groupId, callback) {
        if (orgId && groupId) {
            $.ajax({
                type: "POST",
                url: "api/organizations/" + orgId + "/groups/" + groupId,
                contentType: "application/json"
            })
                .done(function () {
                    if (typeof callback === "function") {
                        callback();
                    }
                })
                .fail(function () {
                    var message = res.getRes('error_unexpected');
                    alert(message);
                });
        } else {
            alert("Cannot grant group access. Invalid orgId and/or groupId.");
        }
    }

    var removeOrgGroupAccess = function (orgId, groupId, callback) {
        if (orgId && groupId) {
            $.ajax({
                type: "DELETE",
                url: "api/organizations/" + orgId + "/groups/" + groupId,
                contentType: "application/json"
            })
                .done(function () {
                    if (typeof callback === "function") {
                        callback();
                    }
                })
                .fail(function () {
                    var message = res.getRes('error_unexpected');
                    alert(message);
                });
        } else {
            alert("Cannot remove group access. Invalid orgId and/or groupId.");
        }
    }

    var toggleOrgGroupAccess = function (doGrantAccess, orgId, groupId, callback) {
        doGrantAccess ? grantOrgGroupAccess(orgId, groupId, callback) : removeOrgGroupAccess(orgId, groupId, callback);
    }

    var deleteAccount = function (orgId) {
        if (orgId) {
            $.ajax({
                type: "DELETE",
                url: "api/organizations/" + orgId,
                contentType: "application/json"
            })
                .done(function () {
                    closeMultiModal('account');
                    window.ePlus.modules.dashboard.refreshWidgetsWithResultType(getEnumValue("resultType", "SUPPORTACCOUNTS"));
                })
                .fail(function () {
                    var message = res.getRes('error_unexpected');
                    alert(message);
                });
        } else {
            alert("Cannot remove account. Invalid orgId");
        }
    }

    var isValidOrgId = function (orgId) {
        return orgId && orgId.length;
    }

    return {
        grantOrgGroupAccess: grantOrgGroupAccess,
        removeOrgGroupAccess: removeOrgGroupAccess,
        toggleOrgGroupAccess: toggleOrgGroupAccess,
        deleteAccount: deleteAccount,
        isValidOrgId: isValidOrgId
    }
})();;
(function (ui, $, _, undefined) {
    var HOURS_PER_DAY = 24;
    var MILLISECONDS_PER_SECOND = 1000;
    var SECONDS_PER_MINUTE = 60;

    var upArrowCssClass = "icon-drop-up-icon-01"; 
    var downArrowCssClass = "icon-drop-down-icon"; 

    var view = {
        UNKNOWN: 0,
        DASHBOARD: 1,
        HOMEPAGE: 2,
        LISTVIEW: 3
    };

    var keyCodes = {
        TAB: 9,
        ENTER: 13,
        ESC: 27,
        UP_ARROW: 38,
        DOWN_ARROW: 40
    };

    function getScrollBarWidth() {
        var $outer = $('<div>').css({ visibility: 'hidden', width: 100, overflow: 'scroll' }).appendTo('body'),
            widthWithScroll = $('<div>').css({ width: '100%' }).appendTo($outer).outerWidth();

        $outer.remove();

        return 100 - widthWithScroll;
    }

    function hasScrollBar(elem) {
        return elem ? elem.scrollHeight > elem.clientHeight : false;
    }

    function destroyCkEditorInstance(ckEditorName) {
        if (CKEDITOR.instances[ckEditorName]) {
            try {
                CKEDITOR.instances[ckEditorName].destroy(true);
            } catch (e) { }
        }
    }

    function getCurrentView() {
        if (!!$('#listContent').length) return view.LISTVIEW;
        if (!!$('#widget-area').length) return view.HOMEPAGE;
        if (!!$('.dashLeftSide', '#interiorPageContent').length || !!$('.dashRightSide', '#interiorPageContent').length) return view.DASHBOARD;
        return view.UNKNOWN;
    }

    // Dashboard UI Functions
    function getDashboardResultType(dashType) {
        var value = $('#selectedResult_' + dashType).val() || '0';
        return parseInt(value);
    }

    function isDashboardResultType(dashType, resultTypes) {
        if (_.isInteger(resultTypes)) {
            resultTypes = [resultTypes];
        }

        var resultType = getDashboardResultType(dashType);
        return resultTypes.indexOf(resultType) > -1;
    }

    function makeRadioChecked($this) {
        $this.removeClass("radio_unchecked");
        $this.addClass("radio_checked");
    }

    function makeRadioUnchecked($this) {
        $this.removeClass("radio_checked");
        $this.addClass("radio_unchecked");
    }

    function isRadioChecked($this) {
        return $this.hasClass("radio_checked");
    }

    function makeBoxChecked($this) {
        $this.removeClass("box_unchecked");
        $this.addClass("box_checked");
    }

    function makeBoxUnchecked($this) {
        $this.removeClass("box_checked");
        $this.addClass("box_unchecked");
    }

    function isBoxChecked($this) {
        return $this.hasClass("box_checked");
    }

    function loadInteriorPageContent(url, callback) {
        $('#interiorPageContent').load(url, callback);
    }

    function clearInteriorPageContent() {
        $('#interiorPageContent').html('');
    }

    function getTimeStamp(activityTime) {
        var date_now = new Date();
        var date_then = new Date(activityTime);

        var seconds = Math.floor(((date_now) - date_then) / MILLISECONDS_PER_SECOND);
        var minutes = Math.floor(seconds / SECONDS_PER_MINUTE);
        var hours = Math.floor(minutes / SECONDS_PER_MINUTE);
        var days = Math.floor(hours / HOURS_PER_DAY);

        if (hours < 1) {
            if (minutes < 2) {
                return getRes('a_minute_ago');
            } else {
                return getRes("x_minutes_ago").replace("{minutes}", minutes);
            }
        } else if (days < 1) {
            if (hours < 2) {
                return getRes('over_an_hour_ago');
            } else {
                return getRes("over_x_hours_ago").replace("{0}", hours);
            }
        } else if (days < 2) {
            return getRes("yesterday");
        } else {
            return getRes("x_days_ago").replace("{days}", days.toLocaleString());
        }
    }

    // General UI
    ui.getCurrentView = getCurrentView;
    ui.hasScrollBar = hasScrollBar;
    ui.scrollBarWidth = getScrollBarWidth();
    ui.view = view;
    ui.keyCodes = keyCodes;
    ui.destroyCkEditorInstance = destroyCkEditorInstance;
    ui.makeRadioChecked = makeRadioChecked;
    ui.makeRadioUnchecked = makeRadioUnchecked;
    ui.isRadioChecked = isRadioChecked;
    ui.makeBoxChecked = makeBoxChecked;
    ui.makeBoxUnchecked = makeBoxUnchecked;
    ui.isBoxChecked = isBoxChecked;
    ui.loadInteriorPageContent = loadInteriorPageContent;
    ui.clearInteriorPageContent = clearInteriorPageContent;
    ui.getTimeStamp = getTimeStamp;
    ui.upArrowCssClass = upArrowCssClass;
    ui.downArrowCssClass = downArrowCssClass;

    // Dashboard UI Functions
    ui.getDashboardResultType = getDashboardResultType;
    ui.isDashboardResultType = isDashboardResultType;
})(window.ePlus.ui = window.ePlus.ui || {}, jQuery, _);;
;
window.ePlus.app = (function () {
    var currentApp = null;

    var App = function (refreshLimitMs) {
        // 3 hours
        var defaultRefreshLimitMs = 3 * 60 * 60 * 1000;
        this.lastRefreshDate = new Date();
        this.refreshLimitMs = refreshLimitMs || defaultRefreshLimitMs;
    };

    App.prototype.doRefresh = function () {
        return Date.now() - this.lastRefreshDate > this.refreshLimitMs;
    };
    
    var init = function (refreshLimitMs) {
        currentApp = new App(refreshLimitMs);
    };

    var refreshAppIfDue = function () {
        if (currentApp && currentApp.doRefresh()) {
            window.location.reload(true);
        }
    }

    return {
        init : init,
        refreshAppIfDue : refreshAppIfDue
    }
})();;
window.ePlus.resources = (function () {
    var getRes = function (key) {
        return (typeof window.res === "object" && window.res && window.res[key]) ? window.res[key] : key;
    };

    return {
        getRes: getRes
    }
})();;
window.ePlus.util = (function () {
    var getEnumValue = function (type, name) {
        return typeof window.enum === "object" && window.enum[type] && window.enum[type][name];
    }

    return {
        getEnumValue: getEnumValue
    }
})();;
ePlus.modules.export = new (function () {
    var self = this;

    this.downloadCSVFromJson = function (exportFileName, columnHeaders, columnFields, data, sheetHeaders, sortProperty) {
        if (sortProperty) {
            data.sort(function (a, b) {
                return a[sortProperty] > b[sortProperty] ? 1 : -1
            });
        }

        var replacer = function (key, value) { return sanitizeForCsvCell(value); }

        var csv = data
            .map(function (row) {
                return columnFields
                    .map(function (fieldName) { return JSON.stringify(row[fieldName], replacer); })
                    .join(',');
                });

        csv.unshift(columnHeaders.join(','));

        if (sheetHeaders) {
            csv.unshift();
            csv.unshift(sheetHeaders);
        }

        csv = csv.join('\r\n');

        var link = document.createElement('a');
        link.setAttribute('href', 'data:text/csv;charset=utf-8,%EF%BB%BF' + encodeURIComponent(csv));
        link.setAttribute('download', exportFileName);
        link.style.visibility = 'hidden';
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    };

    this.flatten = function (data) {
        var result = {};
        function recurse(cur, prop) {
            if (Object(cur) !== cur) {
                result[prop] = cur;
            } else if (Array.isArray(cur)) {
                for (var i = 0, l = cur.length; i < l; i++)
                    recurse(cur[i], prop + "[" + i + "]");
                if (l == 0)
                    result[prop] = [];
            } else {
                var isEmpty = true;
                for (var p in cur) {
                    isEmpty = false;
                    recurse(cur[p], prop ? prop + "." + p : p);
                }
                if (isEmpty && prop)
                    result[prop] = {};
            }
        }
        recurse(data, "");
        return result;
    }

    // source: https://stackoverflow.com/a/6377656/2712286
    var sanitizeForCsvCell = function (str) {
        if (typeof a_string === 'string') {
            var mustQuote = (str.includes(",") || str.includes("\"") || str.includes("\r") || str.includes("\n"));
            if (mustQuote) {
                var sb = "\"";
                for (var i = 0; i < str.length; i++) {
                    sb += str[i];
                    if (str[i] === '"') {
                        sb += "\"";
                    }
                }
                sb += "\"";
                return sb;
            }
        }

        if (str === null || typeof str === 'undefined')
            str = '';

        return str;
    }
})();
;
window.ePlus.modules.comps = new (function () {
    var _this = this;
    var multiGridClosedIconClass = window.ePlus.ui.upArrowCssClass;
    var multiGridOpenIconClass = window.ePlus.ui.downArrowCssClass;

    // Public Functions

    this.addCompMultiGridToggleListenerForAllCompsOfSku = function (sku) {
        var compRows = getCompRowDataForSku(sku);
        compRows.forEach(function (compRow) {
            addCompMultiGridToggleListenerForCompSku(compRow.sku, compRow.compSku);
        });
    }

    this.toggleMultiGridForAllCompsOfAllTitles = function () {
        var $checkbox = $('#showAllCompCrossStoreGridsCheckbox');
        window.enableSingleCheckbox($checkbox);
        var prefVal;
        if (ePlus.ui.isBoxChecked($checkbox)) {
            _this.showMultiGridForAllCompsOfAllTitles();
            prefVal = 1;
        } else {
            hideMultiGridForAllCompsOfAllTitles();
            prefVal = 0;
        }
        var prefType = 'display';
        var prefName = 'showAllCompCrossStoreGrids';
        window.setListViewProperty(prefName, prefVal);
        window.savePreference(prefType, prefName, prefVal);
    }

    this.initializeCompsOfAllTitles = function () {
        var allCompRows = getAllCompRowDataOnPage();

        var skus = _.uniq(_.map(allCompRows, 'sku'));
        skus.forEach(function (sku) {
            _this.makeToggleAllCompMultiGridsBySkuIconShowAllNextClick(sku);
            _this.addCompMultiGridToggleListenerForAllCompsOfSku(sku);
        });

        var compSkus = _.uniq(_.map(allCompRows, 'compSku'));
        var options = {
            skuList: compSkus,
            rowClass: 'Multi',
            selectedOrgId: window.getCompsSelectedOrgId(),
            gridType: 'multi',
            doUseLibraryColumns: window.doUseLibraryColumnsForComps(),
            rowClassIndividual: 'comp-row'
        };
        window.loadCompAnalytics(options);

        if (window.getListViewProperty("showAllCompCrossStoreGrids") == 1) {
            allCompRows.forEach(function (compRow) {
                showMultiGridForCompSkuAndMakeIconOpen(compRow.sku, compRow.compSku);
            });
        }
    };

    this.initializeCompsForSku = function (sku) {
        var compRows = getCompRowDataForSku(sku); 

        _this.makeToggleAllCompMultiGridsBySkuIconShowAllNextClick(sku);
        _this.addCompMultiGridToggleListenerForAllCompsOfSku(sku);

        var compSkus = _.uniq(_.map(compRows, 'compSku'));
        var options = {
            skuList: compSkus,
            rowClass: 'Multi_' + sku,
            selectedOrgId: window.getCompsSelectedOrgId(),
            gridType: 'multi',
            doUseLibraryColumns: window.doUseLibraryColumnsForComps(),
            rowClassIndividual: 'compRow' + sku
        };
        window.loadCompAnalytics(options);

        if (window.getListViewProperty("showAllCompCrossStoreGrids") == 1) {
            compRows.forEach(function (compRow) {
                showMultiGridForCompSkuAndMakeIconOpen(compRow.sku, compRow.compSku);
            });
        }
    }; 

    this.showMultiGridForAllCompsOfAllTitles = function () {
        var allCompRows = getAllCompRowDataOnPage();
        allCompRows.forEach(function (compRow) {
            showMultiGridForCompSkuAndMakeIconOpen(compRow.sku, compRow.compSku);
        });
    };

    this.showMultiGridForAllCompsOfSku = function (sku) {
        var compRows = getCompRowDataForSku(sku);
        compRows.forEach(function (compRow) {
            showMultiGridForCompSkuAndMakeIconOpen(compRow.sku, compRow.compSku);
        });
    }

    this.makeToggleAllCompMultiGridsBySkuIconCollapseAllNextClick = function (sku) {
        var $toggleAllCompMultiGridsBySkuIcon = getToggleAllCompMultiGridsBySkuIcon(sku);
        $toggleAllCompMultiGridsBySkuIcon.attr('title', window.getRes('hide_all_cross_store_grids'));
        $toggleAllCompMultiGridsBySkuIcon.off().on('click', function () {
            hideMultiGridForAllCompsOfSku(sku);
        });
    }

    this.makeToggleAllCompMultiGridsBySkuIconShowAllNextClick = function (sku) {
        var $toggleAllCompMultiGridsBySkuIcon = getToggleAllCompMultiGridsBySkuIcon(sku);
        $toggleAllCompMultiGridsBySkuIcon.attr('title', window.getRes('show_all_cross_store_grids'));
        $toggleAllCompMultiGridsBySkuIcon.off().on('click', function () {
            _this.showMultiGridForAllCompsOfSku(sku);
        });
    }

    // Private Functions 

    var addCompMultiGridToggleListenerForCompSku = function (sku, compSku) {
        var $multiGridToggleIcon = getMultiGridToggleIcon(sku, compSku);
        $multiGridToggleIcon.off().on("click", function () {
            toggleMultiGridAndIconForCompSku(sku, compSku);
        });
    }

    var hideMultiGridForAllCompsOfSku = function (sku) {
        var compRows = getCompRowDataForSku(sku);
        compRows.forEach(function (compRow) {
            hideMultiGridForCompSkuAndMakeIconClosed(compRow.sku, compRow.compSku);
        });
    }

    var toggleMultiGridAndIconForCompSku = function (sku, compSku) {
        var $icon = getMultiGridToggleIcon(sku, compSku);
        if ($icon.hasClass(multiGridClosedIconClass)) {
            showMultiGridForCompSkuAndMakeIconOpen(sku, compSku);
        } else {
            hideMultiGridForCompSkuAndMakeIconClosed(sku, compSku);
        }
    }

    var showMultiGridForCompSkuAndMakeIconOpen = function (sku, compSku) {
        var $icon = getMultiGridToggleIcon(sku, compSku);
        showMultiGridForCompSku(sku, compSku);
        $icon.removeClass(multiGridClosedIconClass);
        $icon.addClass(multiGridOpenIconClass);
        updateToggleAllCompMultiGridsIconState(sku);
    }

    var hideMultiGridForCompSkuAndMakeIconClosed = function (sku, compSku) {
        var $icon = getMultiGridToggleIcon(sku, compSku);
        $icon.removeClass(multiGridOpenIconClass);
        $icon.addClass(multiGridClosedIconClass);
        hideMultiGridForCompSku(sku, compSku);
        updateToggleAllCompMultiGridsIconState(sku);
    }

    var showCompMultiGridLocationColumnFrame = function (sku) {
        var $locationColumnFrame = getCompMultiGridLocationColumnFrame(sku);
        $locationColumnFrame.show();
    }

    var hideCompMultiGridLocationColumnFrame = function (sku) {
        var $locationColumnFrame = getCompMultiGridLocationColumnFrame(sku);
        $locationColumnFrame.hide();
    }

    var showCompMultiGrid = function (sku, compSku) {
        var $multiGridData = getCompMultiGridDataElements(sku, compSku);
        var $multiGridLocationData = getCompMultiGridLocationDataElement(sku, compSku);
        $multiGridData.show();
        $multiGridLocationData.show();
    }

    var hideCompMultiGrid = function (sku, compSku) {
        var $multiGridData = getCompMultiGridDataElements(sku, compSku);
        var $multiGridLocationData = getCompMultiGridLocationDataElement(sku, compSku);
        $multiGridData.hide();
        $multiGridLocationData.hide();
    }

    var showMultiGridForCompSku = function (sku, compSku) {
        if (!isAnyMultiGridShowing(sku)) {
            showCompMultiGridLocationColumnFrame(sku);
        }
        showCompMultiGrid(sku, compSku);
    }

    var hideMultiGridForCompSku = function (sku, compSku) {
        if (!isAnyMultiGridShowing(sku)) {
            hideCompMultiGridLocationColumnFrame(sku);
        }
        hideCompMultiGrid(sku, compSku);
    }

    var isAnyMultiGridShowing = function (sku) {
        var isAnyShowing = false;
        var $multiGridToggleElements = getMultiGridToggleElements(sku);
        if ($multiGridToggleElements != null) {
            $multiGridToggleElements.each(function (i, multiGridToggleElement) {
                if ($(multiGridToggleElement).hasClass(multiGridOpenIconClass)) {
                    isAnyShowing = true;
                    return false;
                }
            })
        }
        return isAnyShowing;
    }

    var areAllMultiGridsShowing = function (sku) {
        var areAllShowing = true;
        var multiGridToggleElements = getMultiGridToggleElements(sku);
        if (multiGridToggleElements != null) {
            multiGridToggleElements.each(function (i, multiGridToggleElement) {
                if ($(multiGridToggleElement).hasClass(multiGridClosedIconClass)) {
                    areAllShowing = false;
                    return false;
                }
            })
        }
        return areAllShowing;
    } 

    var hideMultiGridForAllCompsOfAllTitles = function () {
        var allCompRows = getAllCompRowDataOnPage();
        allCompRows.forEach(function (compRow) {
            hideMultiGridForCompSkuAndMakeIconClosed(compRow.sku, compRow.compSku);
        });
    }

    var updateToggleAllCompMultiGridsIconState = function (sku) {
        if (areAllMultiGridsShowing(sku)) {
            _this.makeToggleAllCompMultiGridsBySkuIconCollapseAllNextClick(sku);
        } else {
            _this.makeToggleAllCompMultiGridsBySkuIconShowAllNextClick(sku);
        }
    }

    var getAllCompRowDataOnPage = function () {
        var compRows = [];
        var $allCompSkuRowsOnPage = getAllCompSkuRowsOnPage();
        $allCompSkuRowsOnPage.each(function () {
            var $this = $(this);
            var compRow = {
                sku: getSkuFromCompSkuRow($this),
                compSku: getCompSkuFromCompSkuRow($this)
            }
            compRows.push(compRow);
        });
        return compRows;
    };

    var getCompRowDataForSku = function (sku) {
        var compRows = [];
        var $compRowsForSku = getCompSkuRowsForSku(sku);
        $compRowsForSku.each(function () {
            var $this = $(this);
            var compRow = {
                sku: getSkuFromCompSkuRow($this),
                compSku: getCompSkuFromCompSkuRow($this)
            }
            compRows.push(compRow);
        });
        return compRows;
    }

    // Private Functions - for retrieving and manipulating HTML elements

    var getAllCompSkuRowsOnPage = function () {
        return $('.comp-row');
    }

    var getCompSkuRowsForSku = function (sku) {
        return $('#compTable' + sku + ' .comp-row');
    }

    var getMultiGridToggleIcon = function (sku, compSku) {
        return $('#multiGrid_' + sku + '_' + compSku);
    }

    var getMultiGridToggleElements = function (sku) {
        return $("#compTable" + sku + " .multiStoreComps");
    }

    var getCompMultiGridLocationColumnFrame = function (sku) {
        return $("#compTable" + sku + " .comp-multiGridLocationColumnFrame");
    }

    var getCompMultiGridLocationDataElement = function (sku, compSku) {
        return $("#compTable" + sku + " .comp-row[data-rowid='" + compSku + "'] .comp-multiGridLocationData");
    }

    var getCompMultiGridDataElements = function (sku, compSku) {
        return $("#compTable" + sku + " .comp-row[data-rowid='" + compSku + "'] .comp-multiGridData");
    }

    var getCompSkuFromCompSkuRow = function ($compRow) {
        return $compRow.attr('data-rowid');
    }

    var getSkuFromCompSkuRow = function ($compRow) {
        return $compRow.attr('data-compsku-for');
    }

    var getToggleAllCompMultiGridsBySkuIcon = function (sku) {
        return $('#compTable' + sku + ' .compsGridCrossLocationGridIcon');
    }
});;
;
ePlus.modules.listView = (function () {
    var res = window.ePlus.resources;

    var getAccountDetails = function (filterTypes) {
        var selectedOrgId = getListViewProperty("selectedOrgID");
        if (!ePlus.modules.org.isValidOrgId(selectedOrgId)) return;

        var postParams = JSON.stringify(window.items);
        var selectedMailingId = getListViewProperty("selectedMailingID");

        $.ajax({
            type: "POST",
            data: postParams,
            cache: false,
            url: "api/me/accounts/" + selectedOrgId + "/details?mailingId=" + selectedMailingId + '&doIncludeMarkupNoteText=false',
            contentType: "application/json"
        })
            .done(function (data) {
                if (data.productSuggestions) {
                    updateSuggestions(data.productSuggestions, filterTypes[3]);
                }

                if (data.orderLineItems) {
                    o.updateOrderSortRefine();
                    calculateSectionTotalsVariable(filterTypes[4]);
                }

                if (data.markupPriorities) {
                    markupPriorities(data.markupPriorities, filterTypes[0]);
                }

                if (data.accountNotes) {
                    accountNotes(data.accountNotes, filterTypes[2]);
                }

                if (data.markupNotes) {
                    markupNotes(data.markupNotes, filterTypes[1]);
                }

                if (data.accountSharedTags) {
                    setAccountSharedTags(data.accountSharedTags, filterTypes[5]);
                }
            });
    };

    var getListViewProperty = function (propName) {
        if (window.listView != null) {
            return window.listView[propName];
        }
        return null;
    };

    var getItemOriginalOrder = function (item) {
        var itemSortRefine = _.find(window.sortrefine, function (sortrefine) {
            return sortrefine.item === item
        });

        return itemSortRefine ? itemSortRefine.originalOrder : 0;
    };

    var setListViewProperty = function (propName, value) {
        if (window.listView != null) {
            window.listView[propName] = value;
            return true;
        }
        return false;
    }

    var toggleCheck = function (item, itemRow) {
        var isCheckmarkChecked = $("#check_" + item).hasClass("checkmark_checked");
        var isNumChecked = $("#num_" + item).hasClass("num_checked");

        if (isCheckmarkChecked || isNumChecked) {
            $("#check_" + item).removeClass("checkmark_checked");
            $("#num_" + item).removeClass("num_checked");
            window.rows[itemRow].selected = 0;
            window.selected -= 1;
            $(".itemAllCheck").removeClass("checkmark_checked");
        } else {
            $("#check_" + item).addClass("checkmark_checked");
            $("#num_" + item).addClass("num_checked")
            window.rows[itemRow].selected = 1;
            window.selected += 1;
        }
        window.getResults();
    };

    function updateSuggestions(productSuggestions, filterType) {
        var suggestionsBySku = Object.keys(productSuggestions)
            .reduce(function (unitSumBySku, sku) {
                var skuUnitSum = productSuggestions[sku]
                    .reduce(function (sum, suggestion) {
                        if (suggestion.storeID !== null && 'units' in suggestion) {
                            var units = parseInt(suggestion.units, 10) || 0;
                            sum = sum || 0;
                            sum += units;
                        }

                        return sum;
                    }, null);

                if (skuUnitSum !== null) {
                    unitSumBySku[sku] = skuUnitSum;
                }

                return unitSumBySku;
            }, []);

        window.sortrefine.forEach(function (item, i) {
            var SUGGESTIONS = 'Suggestions';
            setSortRefineProperty(i, SUGGESTIONS, item.item in suggestionsBySku ? 1 : 0);
        });

        calculateSectionTotalsVariable(filterType);
    }

    function markupPriorities(markupPriorities, filterType) {
        for (var i = 0; window.items && i < window.items.length; i++) {
            if (window.items[i] in markupPriorities) {
                var priorityObject = markupPriorities[window.items[i]];
                setSortRefineProperty(i, "Priority_Markup", priorityObject);
            } else {
                setSortRefineProperty(i, "Priority_Markup", 0);
            }
        }
        calculateSectionTotalsVariable(filterType);
    }

    function accountNotes(accountNotes, filterType) {
        for (var i = 0; window.items && i < window.items.length; i++) {
            if (window.items[i] in accountNotes) {
                setSortRefineProperty(i, "AccountNote", 1);
            } else {
                setSortRefineProperty(i, "AccountNote", 0);
            }
        }
        calculateSectionTotalsVariable(filterType);
    }

    function markupNotes(markupNotes, filterType) {
        for (var i = 0; window.items && i < window.items.length; i++) {
            if (window.items[i] in markupNotes) {
                setSortRefineProperty(i, "Note_Markup", 1);
            } else {
                setSortRefineProperty(i, "Note_Markup", 0);
            }
        }
        calculateSectionTotalsVariable(filterType);
    }

    function sharedMarkupNotes(sharedMarkupNotes, filterType) {
        for (var i = 0; window.items && i < window.items.length; i++) {
            if (window.items[i] in sharedMarkupNotes) {
                setSortRefineProperty(i, "Note_MarkupShared", 1);
            } else {
                setSortRefineProperty(i, "Note_MarkupShared", 0);
            }
        }
        calculateSectionTotalsVariable(filterType);
    }

    function setAccountSharedTags(accountSharedTags, filterType) {
        var allTags = [];
        for (var i = 0; window.items && i < window.items.length; i++) {
            var tags = [];
            if (window.items[i] in accountSharedTags) {
                var skuTags = accountSharedTags[window.items[i]];
                for (var j = 0; j < skuTags.length; j++) {
                    tags.push(skuTags[j].value);
                    allTags.push(skuTags[j].value);
                }
            } else {
                tags.push(res.getRes("untagged_titles"));
            }
            setSortRefineProperty(i, "TagsAccountShared", tags);
        }

        allTags = _.uniq(allTags);

        allTags.sort(function (a, b) {
            return a.toLowerCase().localeCompare(b.toLowerCase());
        });
        allTags.unshift(res.getRes("untagged_titles"));

        populateRefineHTML(filterType, allTags);
        window.refineMap[filterType] = allTags;
    }

    var hideListViewElement = function (resultType, itemId, dashboardType) {
        switch (resultType) {
            case window.getEnumValue('resultType', 'ELIGIBLEAFFILIATIONS'):
                var $affiliation = $('#hide-affiliation-' + itemId).closest('.listview-column-row');
                $affiliation.addClass('excluded');
                $affiliation.addClass('hidden');
            default:
                $('#thumb_' + dashboardType + '_' + itemId).parent().addClass("excluded");
                $('#catThumb_' + dashboardType + '_' + itemId).parent().addClass("excluded");
        }
    };

    var getResultType = function () {
        return getListViewProperty('resultType');
    };

    var getResultTypeEnumValue = function (enumName) {
        return window.getEnumValue('resultType', enumName);
    };

    var getItemType = function () {
        return getListViewProperty('itemType')
    };

    var getItemTypeEnumValue = function (enumName) {
        return window.getEnumValue('itemType', enumName);
    };

    var isResultType = function (enumName) {
        return getResultType() === getResultTypeEnumValue(enumName);
    };

    var isItemType = function (enumName) {
        return getItemType() === getItemTypeEnumValue(enumName);
    };

    var initializeByItemType = function () {
        switch (getItemType()) {
            case getEnumValue('itemType', 'TITLE'):
                runTitleListCleanup();
                break;
            case getEnumValue('itemType', 'REVIEW'):
                ePlus.modules.listView.reviews.initializeReviewsListView();
                break;
            case getEnumValue('itemType', 'CONTACT'):
                initializeContactsListView();
                break;
            case getEnumValue('itemType', 'CONTACTORGANIZATION'):
                initializeContactOrganizationsListView();
                break;
            case getEnumValue('itemType', 'USERREQUESTORDERLINEITEM'):
            case getEnumValue('itemType', 'PRCTITLE'):
                ePlus.modules.listView.prc.initialize();
                break;
            case getEnumValue('itemType', 'IMPRINTGROUP'):
                initializeImprintGroupListView();
            case getEnumValue('itemType', 'USERROLEASSIGNMENT'):
                addRoleAssignmentClickEvents('.remove-assignment-select', '.add-assignment-select');
                break;
        }
    };

    function doLoadEventGridForResultType() {
        return isResultType('TITLE_CATALOG')
            || isResultType('TITLEPUBLICITYCAMPAIGN')
            || isResultType('TITLE_SEARCH')
            || isResultType('TITLE_INDIVIDUAL');
    }

    var initializeByResultType = function () {
        if (doLoadEventGridForResultType()) {
            window.ePlus.modules.eventGrids.initEventGridRequests();
        }
    };

    var listViewItemCountEvaluators = [
        {
            entityType: +getEnumValue('entityType', 'NOTE'),
            doCount: function (item) {
                return item.PersonalNotes == 1;
            }
        },
        {
            entityType: +getEnumValue('entityType', 'REVIEW'),
            doCount: function (item) {
                return item.PersonalReviews > 0;
            }
        },
        {
            entityType: +getEnumValue('entityType', 'TAGTITLE'),
            doCount: function (item) {
                return hasAccountTags(item.Tags_Account);
            }
        }
    ];

    var hasAccountTags = function (tags) {
        return tags != null
            && tags.length > 0
            && tags[0] !== getRes('untagged_titles');
    }

    var updateListViewCounters = function () {
        window.listView.itemCountersByEntityType = window.listView.itemCountersByEntityType || {};

        listViewItemCountEvaluators.forEach(function (evaluator) {
            $qhead = $('#qhead' + evaluator.entityType);

            if ($qhead.length === 0) return;

            $qhead.html(window.listView.itemCountersByEntityType[evaluator.entityType] || 0);
            showHide_qhead(evaluator.entityType);
        });
    }

    var incrementListViewCounters = function (item) {
        window.listView.itemCountersByEntityType = window.listView.itemCountersByEntityType || {};

        listViewItemCountEvaluators
            .filter(function (evaluator) { return evaluator.doCount(item); })
            .forEach(function (evaluator) {
                evaluator.entityType in this
                    ? this[evaluator.entityType]++
                    : this[evaluator.entityType] = 1;
            }, window.listView.itemCountersByEntityType);
    }

    var incrementListViewSelectedCountIfRowSelected = function (row) {
        if (row.selected === 1) { window.selected++; }
    }

    var getSelectedItemIds = function () {
        return (window.rows || [])
            .filter(function (row) { return row.selected == 1; })
            .map(function (row) { return row.item; });
    }

    var refreshAutoComplete = function () {
        if (typeof initializeAutoComplete !== "function") return;
        initializeAutoComplete();
    }

    var refreshSelectAllCheckbox = function () {
        if (window.selected !== 0) return;
        $(".itemAllCheck").removeClass("checkmark_checked");
    }

    var refreshFooterUnitSummary = function () {
        refreshUnitSummary();
    }

    var removeListViewItemAndUpdateListView = function (itemId) {
        $("#as_" + itemId).remove();
        removeListViewItem(itemId);
        updateListView();
    }

    var updateListView = function () {
        updateRefineFilterCounts();
        adjustResultTypeCount(getListViewProperty("resultType"), -1);
        adjustTotalResultsCount(-1);
    }

    var buildListViewRow = function (itemId, selectedItemIds) {
        return {
            item: itemId,
            selected: (selectedItemIds && selectedItemIds.indexOf(itemId) > -1) ? 1 : 0
        };
    }

    var addListViewRowAndIncrementCounters = function (item, selectedItemIds) {
        var row = buildListViewRow(item.item, selectedItemIds);

        incrementListViewCounters(item);
        incrementListViewSelectedCountIfRowSelected(row);

        window.rows.push(row);
    }

    var initialize = function () {
        window.selected = 0;
        window.rows = [];
        window.listView = window.listView || {};
        window.listView.itemCountersByEntityType = {};
    }

    return {
        getAccountDetails: getAccountDetails,
        getListViewProperty: getListViewProperty,
        getItemOriginalOrder: getItemOriginalOrder,
        setListViewProperty: setListViewProperty,
        hideListViewElement: hideListViewElement,
        toggleCheck: toggleCheck,
        isResultType: isResultType,
        isItemType: isItemType,
        initializeByItemType: initializeByItemType,
        initializeByResultType: initializeByResultType,
        getSelectedItemIds: getSelectedItemIds,
        updateListViewCounters: updateListViewCounters,
        refreshAutoComplete: refreshAutoComplete,
        refreshSelectAllCheckbox: refreshSelectAllCheckbox,
        refreshFooterUnitSummary: refreshFooterUnitSummary,
        removeListViewItemAndUpdateListView: removeListViewItemAndUpdateListView,
        buildListViewRow: buildListViewRow,
        addListViewRowAndIncrementCounters: addListViewRowAndIncrementCounters,
        initialize: initialize
    };
})();;
;
ePlus.modules.listView.refinements = (function () {
    var multiValueRefinementTypes = [
        getEnumValue("filterType", "MYTAG"),
        getEnumValue('filterType', 'COLLEAGUETAG'),
        getEnumValue('filterType', 'MARKUPSHAREDTAG'),
        getEnumValue("filterType", "ACCOUNTSHAREDTAGS"),
        getEnumValue('filterType', 'THEMA'),
        getEnumValue('filterType', 'THEMASUBJECTQUALIFIER'),
        getEnumValue('filterType', 'BOUNDVENDOR'),
        getEnumValue('filterType', 'MARKUPTAG'),
        getEnumValue('filterType', 'ALLACCOUNTTAG'),
        getEnumValue('filterType', 'CATEGORIES'),
        getEnumValue('filterType', 'TITLE'),
        getEnumValue('filterType', 'IMPRINTMULTI'),
        getEnumValue('filterType', 'AFFILIATIONMULTI'),
        getEnumValue('filterType', 'HONORS'),
        getEnumValue('filterType', 'GROUPNAMES'),
        getEnumValue('filterType', 'DISTRIBUTORNAME'),
        getEnumValue('filterType', 'DISTRIBUTORSTATUS'),
        getEnumValue('filterType', 'PRCSTATUS'),
        getEnumValue('filterType', 'PRINTABLESTATUS'),
        getEnumValue('filterType', 'MARKUPSHAREDBY'),
        getEnumValue('filterType', 'COMMUNITY'),
        getEnumValue('filterType', 'DRCFORMAT')
    ];

    var isMultiValueRefinement = function (value) {
        var type = parseInt(value, 10);
        return getMultiValueRefinementTypes().indexOf(type) > -1;
    };

    var getMultiValueRefinementTypes = function () {
        return multiValueRefinementTypes;
    };

    var isSortRefineInitialized = function () {
        return _.isArray(window.sortrefine) && window.sortrefine.length > 0;
    };

    var initializeListViewRefinements = function () {
        window.listView = window.listView || {};
        window.listView.refinements = window.listView.refinements || {};
    };

    var doesItemValueMatchRefinement = function (itemValue, refinement) {
        return refinement.isMultiValue && itemValue !== null
            ? itemValue.some(function (value) { return value == refinement.value; })
            : itemValue == refinement.value;
    };

    var doesItemMatchRefinement = function (item, refinement) {
        return refinement.key in item
            && doesItemValueMatchRefinement(item[refinement.key], refinement);
    };

    var doesItemMatchAnyRefinements = function (item, refinements) {
        return refinements.some(function (refinement) {
            return doesItemMatchRefinement(item, refinement);
        });
    };

    var addAppliedRefinement = function (refinements, refinement) {
        refinements[refinement.type] = refinements[refinement.type] || [];
        refinements[refinement.type].push(refinement);

        return refinements;
    }

    var apply = (function () {
        var listView = ePlus.modules.listView;

        var getAppliedRefinementsByType = function () {
            return window.listView.refinements.applied
                .reduce(addAppliedRefinement, {});
        };

        var doesListViewHaveAppliedRefinements = function () {
            return window.listView.refinements.applied
                && window.listView.refinements.applied.length > 0;
        };

        var doesItemMatchAppliedRefinements = function (refinements, item) {
            if (!doesListViewHaveAppliedRefinements()) return true;

            return Object.keys(refinements).every(function (type) {
                return doesItemMatchAnyRefinements(item, refinements[type]);
            });
        };

        var applyRefinementsToListViewItem = function (refinements, item, i) {
            var FILTERED_OUT = 'filteredOut';
            if (doesItemMatchAppliedRefinements(refinements.applied, item)) {
                setSortRefineProperty(i, FILTERED_OUT, 0);
                refinements.items.push(item);
            } else {
                setSortRefineProperty(i, FILTERED_OUT, 1);
            }

            return refinements;
        }

        var applyRefinementsToListViewItems = function () {
            var selectedItemIds = listView.getSelectedItemIds();

            window.sortrefine
                .reduce(applyRefinementsToListViewItem, {
                    items: [],
                    applied: getAppliedRefinementsByType()
                })
                .items
                .forEach(function (item) {
                    listView.addListViewRowAndIncrementCounters(item, selectedItemIds);
                });
        };

        var applyRefinementsToListView = function () {
            applyRefinementsToListViewItems();
            getResults(window.rows.length, window.sortrefine.length);
        };

        var buildAppliedRefinement = function (type, value) {
            return {
                type: type,
                key: convertFilterTypesToNames(type),
                isMultiValue: isMultiValueRefinement(type),
                value: value
            };
        };

        var getAppliedRefinementElements = function () {
            var appliedRefinementContainerElement = document.getElementById('fsDetail');
            var appliedRefinementElements = appliedRefinementContainerElement
                && appliedRefinementContainerElement.getElementsByClassName('activeFilter');

            if (!appliedRefinementElements) return [];

            return [].slice.call(appliedRefinementElements);
        };

        var buildAppliedRefinements = function () {
            return getAppliedRefinementElements().map(function (element) {
                var type = parseInt(element.dataset.type, 10);
                var value = element.dataset.attr;

                return buildAppliedRefinement(type, value);
            });
        };

        var execute = function () {
            if (!isSortRefineInitialized()) return;

            listView.initialize();
            initializeListViewRefinements();

            window.listView.refinements.applied = buildAppliedRefinements();

            applyRefinementsToListView();
            listView.updateListViewCounters();
            listView.refreshFooterUnitSummary();
            listView.refreshSelectAllCheckbox();
            listView.refreshAutoComplete();
        }; 

        return {
            execute: execute,
            buildAppliedRefinements: buildAppliedRefinements,
        };
    })();

    var counts = (function () {
        var listView = ePlus.modules.listView;

        var formatNumber = typeof Intl !== 'undefined'
            ? new Intl.NumberFormat(window.cultureName).format
            : function (value) { return value; };

        var createItemRefinementVisibilityState = function (id) {
            return {
                id: id,
                hidden: [],
                visible: [],
                _isHidden: function (type) {
                    return this.hidden.indexOf(type) > -1;
                },
                _isVisible: function (type) {
                    return this.visible.indexOf(type) > -1;
                },
                set: function (type, doShowItemEvaluator) {
                    if (typeof doShowItemEvaluator === 'function' && doShowItemEvaluator()) {
                        this._show(type);
                    } else {
                        this._hide(type);
                    }
                },
                _show: function (type) {
                    if (this._isVisible(type)) return;
                    this.visible.push(type);
                    this.hidden = this.hidden.filter(function (t) { return t !== type });
                },
                _hide: function (type) {
                    if (this._isHidden(type) || this._isVisible(type)) return;
                    this.hidden.push(type);
                },
                isVisible: function (type) {
                    return this._isHidden(type)
                        ? this.hidden.length - 1 === 0
                        : this.hidden.length === 0;
                }
            };
        };

        var reset = function (types) {
            types.forEach(function (type) {
                $('#refineFilter' + type).removeClass('done');
            });
        };

        var isRefinementAvailable = function (type) {
            return (type in window.listView.refinements.available)
                && Object.keys(window.listView.refinements.available[type]).length > 0;
        };

        var toggleRefinement = function (type) {
            if (isRefinementAvailable(type)) {
                showRefineSection(type);
            } else {
                hideRefineSection(type);
            }
        };

        var resetRefinements = function () {
            var DEFAULT_REFINEMENT_COUNT_DISPLAY = '(0)';
            $('.refCount').html(DEFAULT_REFINEMENT_COUNT_DISPLAY);
            $('.filterOption, .filterOption .menuOption').hide();
            $('.homeOptionRefineArea.done', $('#leftNavRefine')).show();
        };

        var getRefinementCounterElementId = function (type, row) {
            return 'fCount_' + type + '_' + row;
        };

        var getRefinementCounterElement = function (type, row) {
            var counterId = getRefinementCounterElementId(type, row);
            return document.getElementById(counterId);
        };

        var getFormattedRefinementCount = function (type, row) {
            var count = window.listView.refinements.available[type][row];
            return formatNumber(count);
        };

        var updateRefinementCounter = function (counterElement, formattedCount) {
            if (!counterElement) return;

            counterElement.innerHTML = '(' + formattedCount + ')';
            counterElement.parentElement.style.display = '';
        };

        var updateRefinement = function (type, row) {
            var counterElement = getRefinementCounterElement(type, row);
            var formattedCount = getFormattedRefinementCount(type, row);

            updateRefinementCounter(counterElement, formattedCount);
        };

        var updateRefinements = function () {
            Object.keys(window.refineMap).forEach(function (key) {
                var type = parseInt(key, 10);

                if (type in window.listView.refinements.available) {
                    Object.keys(window.listView.refinements.available[type]).forEach(function (row) {
                        updateRefinement(type, row);
                    });
                }

                toggleRefinement(type);
            });
        };

        var getRefinementOptions = function (type) {
            var container = document.getElementById('refine' + type);
            var optionElements = container
                && container.getElementsByClassName('filterRow_' + type);

            if (optionElements) {
                return [].slice.call(optionElements);
            }

            return [];
        };

        var doIncludeItemInRefinementCount = function (item, optionValue, itemValue) {
            return item.isVisible && optionValue == itemValue;
        };

        var incrementMultiValueRefinementCount = function (counter, item, value) {
            counter.getOption(item.type)
                .filter(function (option) { return doIncludeItemInRefinementCount(item, option.dataset.attr, value); })
                .forEach(function (option) { counter.increment(item.type, option.dataset.row); });
        };

        var incrementRefinementCounts = function (counter, item) {
            (item.value || []).forEach(function (value) {
                incrementMultiValueRefinementCount(counter, item, value);
            });
        };

        var incrementRefinementCountForItem = function (counter, item) {
            if (item.isMultiValue) {
                incrementRefinementCounts(counter, item);
            } else if (doIncludeItemInRefinementCount(item)) {
                counter.increment(item.type, item.value);
            }
        };

        var buildRefinementItem = function (type, item, isVisible) {
            var key = convertFilterTypesToNames(type);

            return {
                type: type,
                value: item[key],
                isVisible: isVisible,
                isMultiValue: isMultiValueRefinement(type)
            };
        };

        var calculateItemRefinementVisibility = function (item, refinement) {
            item.visibility.set(refinement.type, function () {
                return doesItemMatchRefinement(item.item, refinement);
            });

            return item;
        };

        var getItemRefinementVisibility = function (item) {
            return window.listView.refinements.applied
                .reduce(calculateItemRefinementVisibility, {
                    item: item,
                    visibility: createItemRefinementVisibilityState(item.item)
                })
                .visibility;
        };

        var incrementRefinementCountsForItem = function (counter, item) {
            var visibility = getItemRefinementVisibility(item);

            Object.keys(window.refineMap)
                .map(function (key) { return parseInt(key, 10); })
                .map(function (type) { return buildRefinementItem(type, item, visibility.isVisible(type)); })
                .forEach(function (refinementItem) { incrementRefinementCountForItem(counter, refinementItem); });
        };

        var createRefinementCounter = function () {
            return {
                options: {},
                counts: {},
                getOption: function (type) {
                    this.options[type] = this.options[type] || getRefinementOptions(type);
                    return this.options[type];
                },
                increment: function (type, row) {
                    this.counts[type] = this.counts[type] || {};
                    this.counts[type][row] = this.counts[type][row] || 0;
                    this.counts[type][row] += 1;
                }
            };
        };

        var calculateAvailableRefinements = function () {
            return window.sortrefine
                .reduce(function (counter, item) {
                    incrementRefinementCountsForItem(counter, item);
                    return counter;
                }, createRefinementCounter())
                .counts;
        };

        var execute = function () {
            if (!isSortRefineInitialized()) return;

            initializeListViewRefinements();

            window.listView.refinements.applied = window.listView.refinements.applied || [];
            window.listView.refinements.available = calculateAvailableRefinements();

            resetRefinements();
            updateRefinements();
            listView.refreshFooterUnitSummary();
        };

        return {
            reset: reset,
            execute: execute
        };
    })();

    return {
        isMultiValueRefinement: isMultiValueRefinement,
        isSortRefineInitialized: isSortRefineInitialized,
        apply: apply,
        counts: counts
    };
})();;
; window.ePlus.http = (function () {
    var ANTIFORGERY_TOKEN_FORM_KEY = '__RequestVerificationToken';
    var ANTIFORGERY_TOKEN_HEADER_KEY = 'X-RequestVerificationToken';
    var HTTP_STATUS_CODE_ERROR_RANGE_START = 400;
    var statusCode = {
        OK: 200,
        CREATED: 201,
        NO_CONTENT: 204,
        BAD_REQUEST: 400,
        UNAUTHORIZED: 401,
        FORBIDDEN: 403,
        NOT_FOUND: 404,
        CONFLICT: 409,
        INTERNAL_SERVER_ERROR: 500,
        NOT_IMPLEMENTED: 501
    };

    var getHttpStatusCodeErrorMessage = function (statusCode) {
        if (statusCode < HTTP_STATUS_CODE_ERROR_RANGE_START) return;

        switch (statusCode) {
            case statusCode.BAD_REQUEST:
                return getRes('error_bad_request');
            case statusCode.FORBIDDEN:
                return getRes('error_permission');
            default:
                return getRes('error_unexpected');
        }
    };

    var getAntiForgeryTokenFromForm = function (formSelector) {
        return $('input[name=' + ANTIFORGERY_TOKEN_FORM_KEY + ']', formSelector || 'body').val();
    };

    var addAntiForgeryTokenToJson = function (json, token) {
        return addAntiForgeryTokenToObject(json, ANTIFORGERY_TOKEN_FORM_KEY, token);
    };

    var addAntiForgeryTokenToHeaders = function (headers, token) {
        return addAntiForgeryTokenToObject(headers, ANTIFORGERY_TOKEN_HEADER_KEY, token);
    };

    var addAntiForgeryTokenToObject = function (target, key, token) {
        if (typeof target !== 'object') return target;

        var source = {};
        source[key] = token || null;

        return Object.assign(target, source);
    };

    return {
        statusCode: statusCode,
        getAntiForgeryTokenFromForm: getAntiForgeryTokenFromForm,
        addAntiForgeryTokenToJson: addAntiForgeryTokenToJson,
        addAntiForgeryTokenToHeaders: addAntiForgeryTokenToHeaders,
        getHttpStatusCodeErrorMessage: getHttpStatusCodeErrorMessage
    };
})();;
(function (gts, $, _, undefined) {
    // Private
    var SEARCH = 'search',
        DOSEARCHONLYMYPUBLISHERS = 'doSearchOnlyMyPublishers',
        PUBDATERANGE = 'pubDateRange',
        SEARCHFIELDS = 'searchFields';

    var res = window.ePlus.resources;

    function initialize() {
        $('.pubDateRange').on('click', function () {
            $('.pubDateRange').removeClass('radio_checked').addClass('radio_unchecked');
            $(this).addClass('radio_checked');
            
            savePubDateRangePreference();
        });

        $('.searchField').on('click', function () {
            $(this).toggleClass('box_checked box_unchecked');
            saveSearchFieldPreference();
        });

        $('.doSearchOnlyMyPublishers').on('click', function () {
            $(this).toggleClass('box_checked box_unchecked');
            saveDoSearchOnlyMyPublishersPreference();
        });
    }

    function savePubDateRangePreference() {
        var $elem = $('.pubDateRange.radio_checked'),
            value = $elem.data('pubdaterange') || '';

        savePreference(SEARCH, PUBDATERANGE, value, function () {
            updateKeywordSearchLabel();
        });
    }

    function saveSearchFieldPreference() {
        var searchFields = $('.searchField.box_checked').map(function () {
            return $(this).data('searchfield');
        }).get();

        var value = JSON.stringify(searchFields);

        savePreference(SEARCH, SEARCHFIELDS, value);
    }

    function saveDoSearchOnlyMyPublishersPreference() {
        var value = $(".doSearchOnlyMyPublishers").hasClass('box_checked') || "";

        savePreference(SEARCH, DOSEARCHONLYMYPUBLISHERS, value, function () {
            updateKeywordSearchLabel();
        });
    }

    function updateKeywordSearchLabel() {
        var label = getKeywordSearchLabel();
        $("#search-left-label").html(label);
    }

    function getKeywordSearchLabel() {
        var pubDateRange = $('.pubDateRange.radio_checked').data('pubdaterange'),
            doSearchOnlyMyPublishers = $('.doSearchOnlyMyPublishers').hasClass('box_checked').toString() || false.toString(),
            value = [pubDateRange, doSearchOnlyMyPublishers].join('.');

        switch (value) {
            case 'All.false': return res.getRes('all_titles');
            case 'Frontlist.false': return res.getRes('frontlist');
            case 'Backlist.false': return res.getRes('backlist');
            case 'All.true': return res.getRes('my_titles');
            case 'Frontlist.true': return res.getRes('my_frontlist');
            case 'Backlist.true': return res.getRes('my_backlist');
        }
    }

    // Public
    gts.initialize = initialize;
})(window.ePlus.modules.globalTitleSearch = window.ePlus.modules.globalTitleSearch || {}, jQuery, _);;
(function (ualc, $, _, undefined) {
    var res = window.ePlus.resources;
    var $elem,
        $userAddressList,
        $userAddressListItems,
        defaultOptions = {
            onListLoad: null,
        },
        settings = {},
        templates = {},
        updateObservers = [];           

    function saveAddress(data) {
        return $.ajax({
            url: '/api/v1/me/addresses/',
            type: 'POST',
            data: JSON.stringify(data),
            dataType: 'json',
            contentType: 'application/json'
        })
    }

    function deleteAddress(addressId) {
        return $.ajax({
            url: '/api/v1/me/addresses/' + addressId,
            type: 'DELETE'
        })
    }

    function setDefaultAddress(addressId) {
        return $.ajax({
            url: '/api/v1/me/addresses/' + addressId + '/default',
            type: 'POST'
        });
    }

    function openAddEditAddress(addressId) {
        var url = '/GetTreelineControl.aspx?controlName=/uc/address/AddEditUserAddressControl.ascx&addressId=' + addressId;

        openDialog({
            url: url,
            title: !addressId ? res.getRes('add_address') : res.getRes('edit_address'),
            isModal: true,
            buttons: [
                {
                    icon: 'icon-save-icon',
                    type: 'submit'
                }
            ],
            onShow: function ($dialog, $form) {
                $form.find('#country-selector').off("change").on("change", function () {
                    var countryCode = $form[0]["country"].value;
                    var stateProvinceAbbreviation = $form[0]["stateProvince"].value;
                    loadStateProvinceSelector("#state-province-input-container", countryCode, stateProvinceAbbreviation);
                });
                $form.find('input:visible').first().select();
                
            },
            onSubmit: function (event, $dialog, $form) {
                $form.off("submit").on("submit", function () {
                    return false;
                });

                if (!$form[0].checkValidity()) {
                    event.preventDefault();
                    return false;
                }

                var data = [$form.serializeJSON()];

                saveAddress(data).done(function () {
                    loadUserAddressList();
                    broadcastChange();
                }).fail(function (response) {
                    if (response && response.responseText) {
                        notifyUserOfBadAddress(response.responseText, addressId);
                    } else {
                        var message = res.getRes('error_unexpected');
                        alert(message);
                    }
                }).always(function () {
                    $dialog.trigger('close');
                });

                return false;
            }
        });
    }

    function notifyUserOfBadAddress(responseText, addressId) {
        var responseTextJson = JSON.parse(responseText)
        var invalidAddressResults = JSON.parse(responseTextJson.message);
        var errorMessage = "";

        if (invalidAddressResults && invalidAddressResults[addressId]) {
            var invalidAddressResults = invalidAddressResults[addressId];

            invalidAddressResults.forEach(function (invalidAddress) {
                errorMessage += invalidAddress.ErrorMessage + "\n";
            });
        } else {
            errorMessage = res.getRes("error_unexpected");
        }

        alert(errorMessage);
    }

    function loadStateProvinceSelector(container, countryCode, stateProvinceAbbreviation) {
        var url = '/GetTreelineControl.aspx?controlName=/uc/address/StateProvinceSelector.ascx&countryCode=' + countryCode + "&stateProvince=" + stateProvinceAbbreviation;
        $(container).load(url);
    }

    function openDeleteAddress(addressId) {
        openDialog({
            title: res.getRes('delete_address'),
            content: templates.deleteAddressPrompt({ description: getAddressDescription(addressId) }),
            isModal: true,
            buttons: [
                {
                    text: res.getRes('yes'),
                    onClick: function (event, $dialog) {
                        deleteAddress(addressId).done(function () {
                            loadUserAddressList();
                            broadcastChange();
                        }).fail(function () {
                            var message = res.getRes('error_unexpected');
                            alert(message);
                        }).always(function () {
                            $dialog.trigger('close');
                        });
                    }
                },
                {
                    text: res.getRes('no'),
                    onClick: function (event, $dialog) {
                        $dialog.trigger('close');
                    }
                }
            ]
        });
    }

    function openSetDefaultAddress(addressId) {
        openDialog({
            title: res.getRes('set_default_address'),
            content: templates.setDefaultAddressPrompt({ description: getAddressDescription(addressId) }),
            isModal: true,
            buttons: [
                {
                    text: res.getRes('yes'),
                    onClick: function (event, $dialog) {
                        setDefaultAddress(addressId).done(function () {
                            loadUserAddressList();
                            broadcastChange();
                        }).fail(function () {
                            var message = res.getRes('error_unexpected');
                            alert(message);
                        }).always(function () {
                            $dialog.trigger('close');
                        });
                    }
                },
                {
                    text: res.getRes('no'),
                    onClick: function (event, $dialog) {
                        $dialog.trigger('close');
                    }
                }
            ]
        });
    }

    function loadUserAddressList() {
        var url = '/GetTreelineControl.aspx?controlName=/uc/address/UserAddressListControl.ascx';

        $elem.load(url, function () {
            $userAddressList = $('ul.userAddressList', $elem),
            $userAddressListItems = $userAddressList.children();

            $('a.addAddress').on('click', function () {
                openAddEditAddress(0);
            });

            $('a.editAddress', $userAddressList).on('click', function () {
                var addressId = getAddressId(this);
                openAddEditAddress(addressId);
            });

            $('a.deleteAddress', $userAddressList).on('click', function () {
                var addressId = getAddressId(this);
                openDeleteAddress(addressId);
            });

            $('a.setDefaultAddress', $userAddressList).on('click', function () {
                var addressId = getAddressId(this);
                openSetDefaultAddress(addressId);
            });

            if (typeof settings.onListLoad === 'function') {
                var count = getAddressCount();
                settings.onListLoad(count);
            }
        });
    }

    function getAddressId(elem) {
        return $(elem).closest('div.userAddress').data('addressid');
    }

    function getAddressDescription(addressId) {
        return $userAddressListItems.find('div.userAddress[data-addressid=' + addressId + '] span.description').text();
    }

    function getAddressCount() {
        return $userAddressListItems.length;
    }

    function buildTemplates() {
        var description = '<span class="bold">{{description}}</span>',
            setDefaultAddressPrompt = res.getRes('set_default_address_prompt').replace('{0}', description),
            deleteAddressPrompt = res.getRes('delete_address_prompt').replace('{0}', description)

        templates.setDefaultAddressPrompt = _.template(setDefaultAddressPrompt);
        templates.deleteAddressPrompt = _.template(deleteAddressPrompt);
    }

    function subscribeToOnAddressChange(handler) {
        if (typeof handler === 'function' && updateObservers.indexOf(handler) === -1) {
            updateObservers.push(handler);
        }
    }

    function unsubscribeToOnAddressChange(handler) {
        if (updateObservers.indexOf(handler) > -1) {
            var idx = updateObservers.indexOf(handler);
            updateObservers.splice(idx, 1);
        }
    }

    function broadcastChange() {
        var retiredObservers = [];
        for (var i = 0; i < updateObservers.length; i++) {
            if (typeof updateObservers[i] === 'function') {
                updateObservers[i]();
            } else {
                retiredObservers.push(updateObservers[i]);
            }
        }
        for (var i = 0; i < retiredObservers.length; i++) {
            unsubscribeToOnAddressChange(retiredObservers[i]);
        }
    }

    function initialize(selector, options) {
        $elem = $(selector);
        settings = $.extend({}, defaultOptions, options);

        buildTemplates();
        loadUserAddressList();
    }

    // Public
    ualc.initialize = initialize;
    ualc.subscribeToOnAddressChange = subscribeToOnAddressChange;
    ualc.unsubscribeToOnAddressChange = unsubscribeToOnAddressChange;
})(window.ePlus.modules.userAddressListControl = window.ePlus.modules.userAddressListControl || {}, jQuery, _);;
; window.ePlus.modules.promotion = (function () {
    var getReviewCopyDetails = function (sku, successCallback) {
        var url = 'api/products/' + sku + '/galleys/details';

        $.ajax({
            type: 'GET',
            url: url,
            cache: false,
            contentType: 'application/json'
        }).done(function (data) {
            if (typeof successCallback === 'function') {
                successCallback(data);
            }
        });
    }

    var createFeaturedTitles = function (titles, callback) {
        if (titles != null && titles.length > 0) {
            $.ajax({
                type: 'POST',
                dataType: 'json',
                contentType: 'application/json; charset=utf-8',
                data: JSON.stringify(titles),
                url: 'api/advertising/featuredTitles/multi'
            }).done(function (data) {
                if (typeof callback === 'function') {
                    callback(data);
                }
            });
        }
    }

    var createFeaturedBanners = function (banners, callback) {
        if (banners != null && banners.length > 0) {
            $.ajax({
                type: 'POST',
                dataType: 'json',
                contentType: 'application/json; charset=utf-8',
                data: JSON.stringify(banners),
                url: 'api/advertising/newsletterbanner/multi'
            }).done(function (data) {
                if (typeof callback === 'function') {
                    callback(data);
                }
            });
        }
    }

    var updateFeaturedBanner = function (banner, callback) {
        if (banner != null) {
            $.ajax({
                type: 'PUT',
                dataType: 'json',
                contentType: 'application/json; charset=utf-8',
                data: JSON.stringify(banner),
                url: 'api/advertising/featuredBanners'
            }).done(function (data) {
                if (typeof callback === 'function') {
                    callback(data);
                }
            });
        }
    }

    var getReviewCopyRequests = function (sku, successCallback) {
        var url = 'api/products/' + sku + '/galleys/requests'
        $.ajax({
            type: 'GET',
            data: postData,
            url: url,
            cache: false,
            contentType: 'application/json'
        }).done(function (data) {
            if (typeof callback === 'function') {
                callback(data);
            }
        });
    }

    var getReviewCopyRequestsCount = function (sku, successCallback) {
        var url = 'api/products/' + sku + '/galleys/requests/counts'
        $.ajax({
            type: 'GET',
            url: url,
            cache: false,
            contentType: 'application/json'
        }).done(function (data) {
            if (typeof callback === 'function') {
                callback(data);
            }
        });
    }

    return {
        getReviewCopyDetails: getReviewCopyDetails,
        getReviewCopyRequests: getReviewCopyRequests,
        createFeaturedTitles: createFeaturedTitles,
        createFeaturedBanners: createFeaturedBanners,
        getReviewCopyRequestsCount: getReviewCopyRequestsCount,
        updateFeaturedBanner: updateFeaturedBanner
    }
})();;
(function (i, $, _, undefined) {
    var res = window.ePlus.resources;
    var defaults = {
            id: 'uploadInsert',
            sku: null,
            isActive: null,
            resources: {
                insert_error_url: null,
                insert_error_maxfile: null,
                insert_error_size: null,
                insert_error_name: null,
                insert_delete_confirm: null,
                upload_insert_ready: null,
                uploading_insert: null,
                error_uploading_insert: null,
                error_unexpected: null
            }
        },
        settings;

    function setMessage(message, isError) {
        $('#uploadInsertMessage').html(message).css({
            color: isError ? '#f00' : '#00f'
        });

        if (isError) {
            console.error(message);
        }
    }

    function validateInsertUpload(insertId) {
        var result;

        switch (insertId) {
            case '-3':
                result = settings.resources.insert_error_url;
                break;
            case '-2':
                result = settings.resources.insert_error_maxfile;
                break;
            case '-1':
                result = settings.resources.insert_error_size;
                break;
            case '0':
                result = settings.resources.insert_error_name;
                break;
        }

        return result;
    }

    function insertUploadComplete(insertId) {
        var message = validateInsertUpload(insertId);

        if (message) {
            setMessage(message, true);
            return;
        }

        closeUploadInsertModal(settings.sku);
        ePlus.modules.listView.setListViewProperty('reload', true);

        if (!settings.isActive) {
            openPaymentModal({
                referenceIds: [insertId],
                sku: ePlus.modules.paymentControl.products.INSERT,
                onClose: function () {
                    closePaymentModal();
                    refreshPromoteModal(settings.sku);
                }
            });
        }
    }

    function insertUploadError() {
        setMessage(settings.resources.error_uploading_insert, true);
    }

    function initializeDefaults() {
        _.each(defaults.resources, function (k, v) {
            defaults.resources[k] = res.getRes(k)
        });
    }

    function initializeInsertAdmin() {
        setMessage(settings.resources.upload_insert_ready);

        $('#' + settings.id).on('submit', function (e) {
            setMessage(settings.resources.uploading_insert);
            e.preventDefault();

            var params = '&' + $.param({
                sku: settings.sku,
                url: $('#url').val()
            });

            ajaxFileUpload('fileToUpload', 'HandleFileUpload', 'Save', 'insert_image', false, true, params, 'fileUploadComplete', insertUploadComplete, insertUploadError)
            return false;
        });

        $('#' + settings.id + ' button.cancel').on('click', function () {
            closeUploadInsertModal(settings.sku);
        });

        $('#' + settings.id + ' button.testUrl').on('click', function () {
            var url = $('#url').val();
            window.open(url, '_blank');
        });

        $('#' + settings.id + ' button.clearUrl').on('click', function () {
            $('#url').val('');
        });

        $('#' + settings.id + ' button.delete').on('click', function () {
            if (confirm(settings.resources.insert_delete_confirm)) {
                $.ajax({
                    type: 'DELETE',
                    url: '/api/products/' + settings.sku + '/insert'
                }).done(function () {
                    ePlus.modules.listView.setListViewProperty('reload', true);
                }).fail(function () {
                    alert(settings.resources.error_unexpected);
                }).always(function () {
                    closeUploadInsertModal(settings.sku);
                    refreshPromoteModal(settings.sku);
                });
            }
        });
    }

    function initialize(config) {
        initializeDefaults();
        settings = $.extend({}, defaults, config);
        initializeInsertAdmin();
    }

    i.initialize = initialize;
})(window.ePlus.modules.promotion.insert = window.ePlus.modules.promotion.insert || {}, jQuery, _);;
; window.ePlus.modules.notes = (function () {
    var populateEmailList = function () {
        for (var i = 0; i < window.noteEmailsToSend.length; i++) {
            $('#select-email-recipients').append(renderEmailToHtml(noteEmailsToSend[i]));
        };
    }

    var getAccountUsersWithEmailAutocomplete = function (autoCompleteJson) {
        $('#personSearchKeywords').unautocomplete();

        var values = autoCompleteJson;

        $('#personSearchKeywords').autocomplete(values, {
            matchContains: true,
            max: 500,
            width: 350,
            formatItem: function (item) {
                return item.label;
            }
        }).result(function (event, item) {
            if (item && _.findIndex(window.noteEmailsToSend, function (o) { return o.value == item.value; }) == -1) {
                $('#select-email-recipients').append(renderEmailToHtml(item));
                $('#personSearchKeywords').val('');
                $('#personSearchKeywords').removeClass('hasPlaceholder');
                getOnEmailListCount();
                window.noteEmailsToSend.push(item);
            }
        });

        $("#personSearchKeywords").focus();
    }

    var removeEmailFromList = function (appUserId) {
        $('#on-list-' + appUserId).remove();
        getOnEmailListCount();
        var index = _.findIndex(window.noteEmailsToSend, function (o) { return o.value == appUserId; });
        if (index > -1) {
            window.noteEmailsToSend.splice(index, 1);
        }
    }

    var getOnEmailListCount = function () {
        $('#num-email').html($('.selected-for-email').length);
    }

    var renderEmailToHtml = function (item) {
        var dHtml = '<div id="on-list-' + item.value + '" class="selected-for-email">';
        dHtml += '<div class="column" data-appuserid="' + item.category + '">' + item.label + '</div>';
        dHtml += '<div class="column-right icon-declined clickable pad-left-5 accFont text-large" onclick="window.ePlus.modules.notes.removeEmailFromList(' + item.value + ');"></div>';
        dHtml += '</div>'
        return dHtml
    }

    var initializeNoteEditor = function (noteId, cultureName) {
        if (CKEDITOR.instances['ticketNotes_' + noteId]) {
            try {
                CKEDITOR.instances['ticketNotes_' + noteId].destroy(true);
            } catch (e) { }
        }

        CKEDITOR.replace('ticketNotes_' + noteId, {
            language: cultureName,
            disableNativeSpellChecker: false,
            width: 400,
            height: 150,
            extraPlugins: "lineutils,widget,image2",
            toolbar: [
                { name: "document", items: ["AjaxSave"] },
                { name: "font", items: ["FontSize"] },
                { name: "styles", items: ["Bold", "Italic", "Underline", "Strike", "RemoveFormat"] },
                { name: "format", items: ["NumberedList", "BulletedList"] },
                { name: "color", items: ["TextColor", "BGColor"] },
                { name: "links", items: ["Image", "Link", "Unlink"] }
            ],
            on: {
                instanceReady: function (e) {
                    var body = e.editor.document.getBody().setStyle("color", "#545454");

                    function initialize() {
                        $dialog.data("editor", e.editor).trigger("show");
                        e.editor.focus();
                    }

                    initialize();
                }
            },
            startupFocus: true
        });
    }

    var openAddEmailRecipients = function () {
        var url = "/GetTreelineControl.aspx?controlName=/uc/email/SelectColleaguesToEmail.ascx";
        openMultiModal({
            id: 'email-recipients',
            url: url,
            width: "300px",
            height: "340px"
        });
    }

    var validateUserNote = function (noteId) {
        var editor;
        try {
            editor = CKEDITOR.instances["ticketNotes_" + noteId];
        } catch (e) { }
        if (!editor) {
            alert("The Editor Did Not Load Correctly");
            return;
        }
        var errorMessage = "";
        var note = editor.getData();
        if (note == "") {
            errorMessage = "You must write some text in the 'Note' field";
        }

        var functionResult = [errorMessage, note];
        return functionResult;
    }

    var initializeOrgNoteEditor = function (onSave, onDelete) {
        CKEDITOR.replace('inline-org-note', {
            language: cultureName,
            disableNativeSpellChecker: false,
            extraPlugins: "ajaxsave,ajaxdelete,lineutils,widget,image2",
            height: 80,
            toolbar: [
                { name: "document", items: ["AjaxSave"] },
                { name: 'delete', items: ['AjaxDelete'] },
                { name: "font", items: ["FontSize"] },
                { name: "styles", items: ["Bold", "Italic", "Underline", "Strike", "RemoveFormat"] },
                { name: "format", items: ["NumberedList", "BulletedList"] },
                { name: "color", items: ["TextColor", "BGColor"] },
            ],
            on: {
                instanceReady: function (e) {
                    var body = e.editor.document.getBody().setStyle("color", "#545454");

                    function initialize() {
                        e.editor.focus();
                    }

                    initialize();
                },
                ajaxsave: onSave,
                ajaxdelete: function (e) {
                    e.editor.setData('');
                    onSave();
                }
            },
            startupFocus: true
        });
    }

    function exportNotes(from, to) {
        window.location.href = 'api/supportTools/notes/export?from=' + from + '&to=' + to;
    }

    return {
        populateEmailList: populateEmailList,
        getAccountUsersWithEmailAutocomplete: getAccountUsersWithEmailAutocomplete,
        removeEmailFromList: removeEmailFromList,
        initializeNoteEditor: initializeNoteEditor,
        openAddEmailRecipients: openAddEmailRecipients,
        validateUserNote: validateUserNote,
        initializeOrgNoteEditor: initializeOrgNoteEditor,
        exportNotes: exportNotes
    }
})();
;
;
window.ePlus.modules.nps = (function () {
    var exportNps = function (from, to, type) {
        window.location.href = 'api/supportTools/nps/export?from=' + from + '&to=' + to + '&type=' + type;
    }

    return {
        exportNps: exportNps
    }
})();;
; window.ePlus.modules.support = (function () {
    var init = function (subscriptionId, resourceGroup, component) {
        this.subscriptionId = subscriptionId || '1f990888-a2f5-4dac-9109-767125cb1f0f';
        this.resourceGroup = resourceGroup || 'Default-ApplicationInsights-EastUS';
        this.component = component || 'Edelweiss Plus';
    }

    var loadAccountNotes = function (menuItem, pageSource, orgId, clientId) {
        $.url = "/GetTreelineControl.aspx?controlName=/uc/support/clientNotes.ascx&pageSource=" + pageSource + "&orgID=" + orgId + "&clientId=" + clientId;
        $("#account-section-" + menuItem)
            .addClass("progressBackground")
            .load($.url, function () {
                $("#account-section-" + menuItem).removeClass("progressBackground");
                $("#account-modal-" + menuItem + "-count")
                    .html($(".account-note", $("#account-section-" + menuItem)).length)
                    .removeClass("hidden");
            });
    }

    var loadUserNotes = function (menuItem, pageSource, appUserId) {
        $.url = "/GetTreelineControl.aspx?controlName=/uc/support/clientNotes.ascx&pageSource=" + pageSource + "&appUserID=" + appUserId;
        $("#section_" + menuItem)
            .addClass("progressBackground")
            .load($.url, function () {
                $("#section_" + menuItem).removeClass("progressBackground");
                $("#user-modal-" + menuItem + "-count")
                    .html($(".account-note", $("#section_" + menuItem)).length)
                    .removeClass("hidden");
            });
    }

    var loadAccountAnalytics = function (menuItem, orgId) {
        $.url = "/GetTreelineControl.aspx?controlName=/uc/support/analytics/accountAnalytics.ascx&orgID=" + orgId;
        $("#account-section-" + menuItem)
            .addClass("progressBackground")
            .load($.url, function () {
                $("#account-section-" + menuItem).removeClass("progressBackground");
            });
    }

    var loadAnalyticsLocations = function (menuItem, orgId) {
        $.url = "/GetTreelineControl.aspx?controlName=/uc/support/accountLocations.ascx&orgId=" + orgId;
        $("#account-section-" + menuItem)
            .addClass("progressBackground")
            .load($.url, function () {
                $("#account-section-" + menuItem).removeClass("progressBackground");
                $("#account-modal-" + menuItem + "-count")
                    .html($(".account-locations", $("#account-section-" + menuItem)).length)
                    .removeClass("hidden");
            });
    }

    var loadConsortiumMembers = function (menuItem, orgId) {
        $.url = "/GetTreelineControl.aspx?controlName=/uc/support/consortiumMembers.ascx&orgId=" + orgId;
        $("#account-section-" + menuItem)
            .addClass("progressBackground")
            .load($.url, function () {
                $("#account-section-" + menuItem).removeClass("progressBackground");
                $("#account-modal-" + menuItem + "-count")
                    .html($(".member-library", $("#account-section-" + menuItem)).length)
                    .removeClass("hidden");
            });
    }

    var loadPublisherPromotions = function (menuItem, orgId) {
        $.url = "/GetTreelineControl.aspx?controlName=/uc/organization/publisherPromotions.ascx&orgId=" + orgId;
        $("#account-section-" + menuItem)
            .addClass("progressBackground")
            .load($.url, function () {
                $("#account-section-" + menuItem).removeClass("progressBackground");
                $("#account-modal-" + menuItem + "-count")
                    .html($(".publisher-promotion", $("#account-section-" + menuItem)).length)
                    .removeClass("hidden");
            });
    }

    var loadAccountAdvocacy = function (menuItem, orgId) {
        $.url = "/GetTreelineControl.aspx?controlName=/uc/support/advocacy/accountAdvocacy.ascx&orgId=" + orgId;
        $("#account-section-" + menuItem)
            .addClass("progressBackground")
            .load($.url, function () {
                $("#account-section-" + menuItem).removeClass("progressBackground");
                $("#account-modal-" + menuItem + "-count")
                    .html($(".advocacy-assessment", $("#account-section-" + menuItem)).length)
                    .removeClass("hidden");
            });
    }

    var loadPublisherRelationships = function (menuItem, orgId) {
        $.url = "/GetTreelineControl.aspx?controlName=/uc/support/managePublisherRelationships.ascx&orgId=" + orgId;
        $("#account-section-" + menuItem)
            .addClass("progressBackground")
            .load($.url, function () {
                $("#account-section-" + menuItem).removeClass("progressBackground");
                $("#account-modal-" + menuItem + "-count")
                    .html($(".publisher-relationship", $("#account-section-" + menuItem)).length)
                    .removeClass("hidden");
                if ($('#pub-rel-' + orgId + '-0').length > 0) {
                    $('#pub-rel-' + orgId + '-0').html($(".pub-relationship-0", $("#relationship-list-" + orgId)).length);
                    $('#pub-rel-' + orgId + '-1').html($(".pub-relationship-1", $("#relationship-list-" + orgId)).length);
                    $('#pub-rel-' + orgId + '-0').toggleClass('hidden', $('#pub-rel-' + orgId + '-0').html() === 0);
                    $('#pub-rel-' + orgId + '-1').toggleClass('hidden', $('#pub-rel-' + orgId + '-1').html() === 0);
                }
            });
    }

    var loadDrcProfiles = function (menuItem, orgId) {
        $.url = "/GetTreelineControl.aspx?controlName=/uc/support/manageDrcProfiles.ascx&orgId=" + orgId;
        $("#account-section-" + menuItem)
            .addClass("progressBackground")
            .load($.url, function () {
                $("#account-modal-" + menuItem + "-count").html($(".drc-profile", $("#account-section-" + menuItem)).length);
                $("#account-section-" + menuItem).removeClass("progressBackground");
            });
    }

    var loadImprintsInCatalogs = function (menuItem, orgId) {
        $.url = "/GetTreelineControl.aspx?controlName=/uc/organization/viewImprintsInCatalogs.ascx&orgId=" + orgId;
        $("#account-section-" + menuItem)
            .addClass("progressBackground")
            .load($.url, function () {
                $("#account-modal-" + menuItem + "-count").html($(".imprint-row", $("#account-section-" + menuItem)).length);
                $("#account-section-" + menuItem).removeClass("progressBackground");
            });
    }

    var loadAccountAnalyticsUsage = function (menuItem, orgId) {
        $.url = "/GetTreelineControl.aspx?controlName=/uc/support/analytics/analyticsUsage.ascx&orgID=" + orgId;
        $("#account-section-" + menuItem)
            .addClass("progressBackground")
            .load($.url, function () {
                $("#account-section-" + menuItem).removeClass("progressBackground");
            });
    }

    var loadAccountNps = function (menuItem, resultType, orgId) {
        $.url = "/GetTreelineControl.aspx?controlName=/uc/dashboard_v2/DashItems.ascx&orgID=" + orgId + "&resultType=" + resultType + "&laneID=0&widgetID=0";
        $("#account-section-" + menuItem)
            .addClass("progressBackground")
            .load($.url, function () {
                $("#account-section-" + menuItem).removeClass("progressBackground");
            });
    }

    var loadAccountBilling = function (menuItem, clientId) {
        $("#account-section-" + menuItem).addClass("progressBackground");
        $.url = "/GetTreelineControl.aspx?controlName=/uc/organization/accountBilling.ascx&clientID=" + clientId;
        $("#account-section-" + menuItem)
            .addClass("progressBackground")
            .load($.url, function () {
                $("#account-section-" + menuItem).removeClass("progressBackground");
        });
    }

    var loadUserNps = function (menuItem, resultType, appUserId) {
        $.url = "/GetTreelineControl.aspx?controlName=/uc/dashboard_v2/DashItems.ascx&appUserID=" + appUserId + "&resultType=" + resultType + "&laneID=0&widgetID=0";
        $("#section_" + menuItem)
            .addClass("progressBackground")
            .load($.url, function () {
                $("#section_" + menuItem).removeClass("progressBackground");
            });
    }

    var getAccountUserCount = function (menuItem, orgId) {
        var url = "api/organizations/"+ orgId + "/users/count";
        $.ajax({
            type: "GET",
            url: url,
            cache: false,
            contentType: "application/json",
            success: function (data) {
                $("#account-modal-" + menuItem + "-count").html(data);
                if (data === 0) {
                    $("#delete-account").removeClass("hidden");
                }
            }
        });
    }

    var getAccountNpsScore = function (menuItem, orgId) {
        var url = "api/organizations/" + orgId + "/nps";
        $.ajax({
            type: "GET",
            url: url,
            cache: false,
            contentType: "application/json",
            success: function (data) {
                if (+data.count > 0) {
                    var score;
                    data.score ? score = data.score : score = 0;
                    $("#account-modal-" + menuItem + "-count").html(score);
                    if (score > 60) {
                        $("#account-modal-" + menuItem + "-count").css("background-color", "green");
                        $("#account-modal-" + menuItem + "-count").css("color", "white");
                    }
                    if (score > 30 && score <= 60) {
                        $("#account-modal-" + menuItem + "-count").css("background-color", "yellow");
                    }
                    if (score > 0 && score <= 30) {
                        $("#account-modal-" + menuItem + "-count").css("background-color", "orange");
                    }
                    if (score <= 0) {
                        $("#account-modal-" + menuItem + "-count").css("background-color", "red");
                        $("#account-modal-" + menuItem + "-count").css("color", "white");
                    }
                } else {
                    $("#account-modal-" + menuItem + "-count").addClass("hidden");
                }

            }
        });
    }

    var getUserNpsScore = function (menuItem, appUserId) {
        var url = "api/users/" + appUserId + "/nps?maxResult=1";
        $.ajax({
            type: "GET",
            url: url,
            cache: false,
            contentType: "application/json",
            success: function (data) {
                if (data && data.length > 0) {
                    $("#user-modal-" + menuItem + "-count").html(data[0].rating);
                    if (data[0].rating > 8) {
                        $("#user-modal-" + menuItem + "-count").css("background-color", "green");
                        $("#user-modal-" + menuItem + "-count").css("color", "white");
                    }
                    if (data[0].rating > 6 && data[0].rating <= 8) {
                        $("#user-modal-" + menuItem + "-count").css("background-color", "yellow");
                    }
                    if (data[0].rating > 3 && data[0].rating <= 6) {
                        $("#user-modal-" + menuItem + "-count").css("background-color", "orange");
                    }
                    if (data[0].rating <= 3) {
                        $("#user-modal-" + menuItem + "-count").css("background-color", "red");
                        $("#user-modal-" + menuItem + "-count").css("color", "white");
                    }
                    $("#user-modal-" + menuItem + "-count").removeClass("hidden");
                } else {
                    $("#user-modal-" + menuItem + "-count").addClass("hidden");
                }
            }
        });
    }

    var loadAccountPeopleScroller = function (orgId, scrollType) {
        $.url = "/GetTreelineControl.aspx?controlName=/uc/contacts/scrollers/userScroller.ascx&orgId=" + orgId + "&height=410&scrollType=" + scrollType;
        $("#userHolder")
            .addClass("progressBackground")
            .load($.url, function () {
                $("#userHolder").removeClass("progressBackground");
            });
    }

    var loadAccountBillingProfile = function (orgId) {
        $.url = "/GetTreelineControl.aspx?controlName=/uc/support/billingProfiles.ascx&orgID=" + orgId;
        $("#accountBillingProfileSection")
            .addClass("progressBackground")
            .load($.url, function () {
                $("#accountBillingProfileSection").removeClass("progressBackground");
            });
    }

    var loadUserSupportProfile = function (appUserId) {
        $.url = "/GetTreelineControl.aspx?controlName=/uc/user/userGeneral_v2.ascx&view=support&appUserID=" + appUserId;
        $("#userGeneralSection")
            .addClass("progressBackground")
            .load($.url, function () {
                $("#userGeneralSection").removeClass("progressBackground");
            });
    }

    var loadUserSupportIFrame = function (menuItem, appUserId) {
        $.url = "/GetTreelineControl.aspx?controlName=/uc/user/userIFrame.ascx&appUserID=" + appUserId;
        $("#section_" + menuItem)
            .addClass("progressBackground")
            .load($.url, function () {
                $("#section_" + menuItem).removeClass("progressBackground");
            });
    }

    var loadAccountIFrame = function (menuItem, orgId) {
        $.url = "/GetTreelineControl.aspx?controlName=/uc/organization/organizationIFrame.ascx&orgId=" + orgId;
        $("#account-section-" + menuItem)
            .addClass("progressBackground")
            .load($.url, function () {
                $("#account-section-" + menuItem).removeClass("progressBackground");
            });
    }

    var deleteNote = function (ticketId) {
        if (confirm("Are you sure want to delete this note?")) {
            var url = "api/supportTools/notes?ticketId=" + ticketId;
            $.ajax({
                type: "DELETE",
                url: url,
                cache: false,
                contentType: "application/json"
            }).done(function () {
                processCompletedAction();
            });
        }
    }

    var updateNote = function (ticketId) {
        var editor;
        try {
            editor = CKEDITOR.instances["ticketNotes_" + ticketId];
        } catch (e) { }
        if (!editor) {
            alert("The Editor Did Not Load Correctly");
            return;
        }
        var errorMessage = "";
        var note = editor.getData();
        if (note == "") {
            errorMessage = "You must write some text in the 'Note' field";
        }
        if (errorMessage == "") {
            var topicId = 0;
            if ($('#ticketTopicSelect').length > 0) {
                topicId = $('#ticketTopicSelect').val();
            }
            var url = "api/supportTools/notes?ticketId=" + ticketId + "&topicId=" + topicId;
            $.ajax({
                type: "PUT",
                url: url,
                cache: false,
                contentType: "application/json",
                data: JSON.stringify(note)
            }).done(function () {
                processCompletedAction();
            });
        } else {
            alert(errorMessage);
        }
    }

    var createNote = function (ticketLevel, appUserId, orgId) {
        var editor;
        try {
            editor = CKEDITOR.instances["ticketNotes_0"];
        } catch (e) { }
        if (!editor) {
            alert("The Editor Did Not Load Correctly");
            return;
        }
        var errorMessage = "";
        var note = editor.getData();
        if (note == "") {
            errorMessage = "You must write some text in the 'Note' field";
        }
        
        var url = "api/supportTools/notes?noteLevel=" + ticketLevel;
        if (appUserId > 0) {
            url += "&appUserId=" + appUserId + "&topicId=" + $('#ticketTopicSelect').val();
        } else {
            url += "&orgId=" + orgId + "&topicId=" + $('#ticketTopicSelect').val();
        }
        if (errorMessage == "") {
            $.ajax({
                type: "POST",
                url: url,
                cache: false,
                contentType: "application/json",
                data: JSON.stringify(note)
            }).done(function () {
                processCompletedAction();
            });
        } else {
            alert(errorMessage);
        }
    }

    var getActiveClientAutoComplete = function (inputPopulate, source) {
        $("#" + inputPopulate).unautocomplete();
        var values = ""
        $.getJSON("/GetJSONData.aspx?m=Organization&builder=GetClientAutoComplete", values, function (data) {
            if (data.code == "SUCCESS") {
                var values = $.parseJSON(data.data);

                $("#" + inputPopulate).autocomplete(values, {
                    matchContains: true,
                    max: 2000,
                    width: 550,
                    formatItem: function (item) {
                        return item.label;
                    }
                }).result(function (event, item) {
                    if (item) {
                        var keywords = item.label;
                        $("#" + inputPopulate).val(keywords);
                        if (source == "main") {
                            processClient(item.value, item.category)    //.value is the ClientID, .category is the OrgID
                        } else {
                            $("#selClientID").val(item.value)
                        }
                    }
                });
            }
            else {
                alert(data.text);
            }
        });
    }

    var grabTitleOwnershipClaim = function (requestId) {
        var $grabbedByMe = getGrabbedTitleOwnershipClaimByMeImageSelector(requestId);
        var $ungrabbed = getUngrabbedTitleOwnershipClaimImageSelector(requestId);
        var $markAsProcessed = getMarkTitleOwnershipClaimAsProcessedImage(requestId);
        var $comment = getCommentOnTitleOwnershipClaimImage(requestId);
        $grabbedByMe.removeClass("grabbedTitleOwnershipClaimImage");
        $grabbedByMe.addClass("grabbedTitleOwnershipClaimImageNextToMarkAsProcessed");
        $grabbedByMe.removeClass("hidden");
        $comment.removeClass("hidden");
        $markAsProcessed.removeClass("hidden");
        $ungrabbed.addClass("hidden");
        $.ajax({
            type: 'POST',
            cache: false,
            url: 'api/v1/analysis/titleOwnershipClaim/treelineSupportGrab/' + requestId,
            error: function () {
                alert(window.getRes("error_unexpected"));
            }
        });
    }; 

    var putBackTitleOwnershipClaim = function (requestId) {
        var $grabbedByMe = getGrabbedTitleOwnershipClaimByMeImageSelector(requestId);
        var $grabbedByInitialUser = getGrabbedTitleOwnershipClaimByInitialUserImageSelector(requestId);
        var $ungrabbed = getUngrabbedTitleOwnershipClaimImageSelector(requestId);
        var $markAsProcessed = getMarkTitleOwnershipClaimAsProcessedImage(requestId);
        var $comment = getCommentOnTitleOwnershipClaimImage(requestId);
        $grabbedByMe.addClass("hidden");
        $grabbedByInitialUser.addClass("hidden");
        $markAsProcessed.addClass("hidden");
        $comment.addClass("hidden");
        $ungrabbed.removeClass("hidden");
        $.ajax({
            type: 'POST',
            cache: false,
            url: 'api/v1/analysis/titleOwnershipClaim/treelineSupportPutBack/' + requestId,
            error: function () {
                alert(window.getRes("error_unexpected"));
            }
        });
    };

    var markTitleOwnershipClaimAsProcessed = function (requestId) {
        var $vertListItem = getVertListItemForTitleOwnershipClaim(requestId);
        $vertListItem.hide(); 
        $.ajax({
            type: 'POST',
            cache: false,
            url: 'api/v1/analysis/titleOwnershipClaim/treelineSupportMarkAsProcessed/' + requestId,
            error: function () {
                alert(window.getRes("error_unexpected"));
            }
        });
    };

    var saveCommentOnTitleOwnershipClaim = function (requestId, comment, $commentPopoverTarget) {
        $commentPopoverTarget.webuiPopover("hide");
        var doneSavingPromise = new window.Promise(function (resolve, reject) {
            $.ajax({
                type: 'POST',
                cache: false,
                data: JSON.stringify(comment),
                contentType: 'application/json',
                url: 'api/v1/analysis/titleOwnershipClaim/treelineSupportSaveComment/' + requestId,
                error: function () {
                    alert("Error saving comment: " + comment);
                    reject();
                },
                success: function () {
                    alert("Comment saved.");
                    resolve();
                }
            });
        });
        return doneSavingPromise;
    };

    var getGrabbedTitleOwnershipClaimByInitialUserImageSelector = function (requestId) {
        return $(".grabbedTitleOwnershipClaimByInitialUserImage", getVertListItemForTitleOwnershipClaim(requestId));
    };
    var getGrabbedTitleOwnershipClaimByMeImageSelector = function (requestId) {
        return $(".grabbedTitleOwnershipClaimByMeImage", getVertListItemForTitleOwnershipClaim(requestId));
    };
    var getUngrabbedTitleOwnershipClaimImageSelector = function (requestId) {
        return $(".ungrabbedTitleOwnershipClaimImage", getVertListItemForTitleOwnershipClaim(requestId));
    };
    var getMarkTitleOwnershipClaimAsProcessedImage = function (requestId) {
        return $(".markTitleOwnershipClaimAsProcessedImage", getVertListItemForTitleOwnershipClaim(requestId));
    };
    var getCommentOnTitleOwnershipClaimImage = function (requestId) {
        return $(".commentOnTitleOwnershipClaimImage", getVertListItemForTitleOwnershipClaim(requestId));
    };
    var getVertListItemForTitleOwnershipClaim = function (requestId) {
        return $(".vertListItemTitleOwnershipClaim[data-requestId='" + requestId + "']");
    };

    var supportGrabNps = function (addInt, commentID) {
        var values = {
            add: addInt,
            id: commentID
        };
        if (addInt == 1) {
            $(".gotIt_" + commentID, $("#interiorPageContent")).show();
            $(".takeIt_" + commentID, $("#interiorPageContent")).hide();
        } else {
            $(".gotIt_" + commentID, $("#interiorPageContent")).hide();
            $(".takeIt_" + commentID, $("#interiorPageContent")).show();
        }
        $.post("/getJSONData.aspx?m=User&builder=ManageGotIt", values, function () { }, "json");
    }

    var toggleSupportWidgets = function (orgId, userId, prefType, prefName) {
        var status = $('#support-toggle').attr('data-status');
        if (status == 1) {
            $('#support-toggle').html('Show Treeline Widgets');
            $('#support-toggle').attr('data-status', 0);
            $('.supportWidget').hide();
        } else {
            status = 0
            $('#support-toggle').html('Hide Treeline Widgets');
            $('#support-toggle').attr('data-status', 1);
            $('.supportWidget').show();
        }

        var url = 'api/users/' + orgId + '/' + userId + '/preferences/' + prefType + '/' + prefName;

        $.ajax({
            type: 'POST',
            data: status,
            cache: false,
            url: url,
            contentType: 'application/json'
        });
    }

    var checkSupportWidgetVisibility = function (orgId, userId, prefType, prefName) {
        var url = 'api/users/' + orgId + '/' + userId + '/preferences/' + prefType + '/' + prefName;
        $.ajax({
            type: 'GET',
            url: url,
            cache: false,
            contentType: 'application/json',
            success: function (data) {
                if (data && data == 1) {
                    toggleSupportWidgets();
                }
            }
        });
    }

    var setUserPreference = function (orgId, userId, prefType, prefName, data) {
        var url = 'api/users/' + orgId + '/' + userId + '/preferences/' + prefType + '/' + prefName;

        $.ajax({
            type: 'POST',
            data: JSON.stringify(data),
            cache: false,
            url: url,
            contentType: 'application/json'
        });
    }

    var openFreshdeskAccount = function (freshDeskNumber) {
        window.open('//abovethetreeline.freshdesk.com/companies/' + freshDeskNumber, '_blank', 'location=yes,height=570,width=820,scrollbars=yes,status=yes');
    }

    var openFreshdeskTicket = function (freshDeskTicketId) {
        window.open('//abovethetreeline.freshdesk.com/helpdesk/tickets/' + freshDeskTicketId, '_blank', 'location=yes,height=670,width=820,scrollbars=yes,status=yes');
    }

    var getFreshdeskStatus = function (status) {
        if (status == 5) {
            return 'Closed'
        };
        if (status == 2) {
            return "<span style='color:red;'>Open</span>"
        };
        if (status == 3) {
            return 'Pending'
        };
        if (status == 4) {
            return 'Resolved'
        };
    }

    var readPageParameter = function (param) {
        var paramValues = param.split('=');
        if (paramValues[0] == 'dashList') {
            var dashType = paramValues[1];
            if (dashType == parseInt(getEnumValue('dashType', 'DASHDRC'))) { return 'DRCs' }
            if (dashType == parseInt(getEnumValue('dashType', 'DASHMYREADING'))) { return 'Shelves' }
            if (dashType == parseInt(getEnumValue('dashType', 'DASHCATALOG'))) { return 'Catalog List' }
            if (dashType == parseInt(getEnumValue('dashType', 'DASHTAG'))) { return 'Tags' }
            if (dashType == parseInt(getEnumValue('dashType', 'DASH_PUBLISHER'))) { return 'Publishers' }
            if (dashType == parseInt(getEnumValue('dashType', 'DASH_PEOPLE'))) { return 'People' }
            if (dashType == parseInt(getEnumValue('dashType', 'DASH_ORDER'))) { return 'Orders' }
            if (dashType == parseInt(getEnumValue('dashType', 'DASHBUZZ'))) { return 'Buzz' }
            if (dashType == parseInt(getEnumValue('dashType', 'DASHPUBLICITYCAMPAIGN'))) { return 'Grids' }
            if (dashType == parseInt(getEnumValue('dashType', 'DASHREVIEW'))) { return 'Reviews' }
        } else if (paramValues[0] == 'sess' || paramValues[0] == '_') {
            return '';
        } else if (paramValues[0] == '' || paramValues[0] == 'dashboard') {
            return 'Dashboard';
        } else if (paramValues[0] == '#publisher') {
            return 'Publisher Page: ' + paramValues[1];
        } else {
            return param;
        }
    }

    var getPageDescriptionFromUrl = function (pageSource) {
        var result = '';
        var pageHashParameters = pageSource.split('&');

        for (var i = 0; i < pageHashParameters.length; i++) {
            var tempDisplay = window.ePlus.modules.support.readPageParameter(pageHashParameters[i].replace('?', '').replace('#', '').replace(/homepage.aspx/ig, ''));
            if (tempDisplay != '') {
                if (result != '') {
                    result += ', ';
                }
                result += tempDisplay;
            }
        }
        return result
    }

    var renderLoadEventRow = function (dataRow) {
        var duration = parseInt(dataRow.duration / 1000);
        var d = new Date(dataRow.timestamp);
        var background = window.ePlus.modules.support.getDurationBackgroundColor(duration);

        var currentDate = new Date(Date.now())
        var dateDisplay = d.toDateString();
        if (currentDate.toDateString() == d.toDateString()) {
            dateDisplay = 'Today';
        }
        var dHtml = '<div style="margin-bottom: 5px; background-color: ' + background + '; border-bottom: 2px solid #545454; padding: 5px;">';
        dHtml += '<div style="clear: both;">';
        dHtml += '<div class="column"><span style="font-weight: bold">' + dateDisplay + '</span>, ' + d.toLocaleTimeString('en-US') + '</div>';
        dHtml += '<div class="columnRight"><a href="#" title="Open in App Insights Analytics" onclick="window.ePlus.modules.support.openInAppInsightsAnalytics({ itemId:\'' + dataRow.itemId + '\', sessionId:\'' + dataRow.sessionId + '\', operationId:\'' + dataRow.operationId + '\', timestamp:\'' + dataRow.timestamp + '\', duration:' + dataRow.duration + '}, event)">' + dataRow.sessionId + '</a> | ' + duration.toLocaleString('US') + ' Seconds</div>';
        dHtml += '<div style="clear: both;"></div>';
        dHtml += '</div><div style="clear: both;">';
        dHtml += '<div class="column" style="cursor: pointer;" onclick="javascript:window.ePlus.modules.support.redirectToPage(\'' + window.ePlus.modules.support.removeSingleQuotes(dataRow.url) + '\')">' + window.ePlus.modules.support.getPageDescriptionFromUrl(window.ePlus.modules.support.removeSingleQuotes(dataRow.url.replace("https://www.edelweiss.plus/", ""))) + '</div>';
        dHtml += '<div class="columnRight">' + dataRow.clientOS + ", " + dataRow.clientBrowser + '</div>';
        dHtml += '<div style="clear: both;"></div>';
        dHtml += '</div>';
        dHtml += '</div>';
        return dHtml;
    }

    var removeSingleQuotes = function (str) {
        return str.replace(/'/g, escape);
    }

    var getDurationBackgroundColor = function (duration) {
        var background = "#ffffff";
        if (duration >= 30) {
            background = "#f49973";
        } else if (duration >= 10) {
            background = "#FFEA00";
        }
        return background;
    }

    var redirectToPage = function (url) {
        window.open(url, '_blank', 'location=yes,height=670,width=820,scrollbars=yes,status=yes');
    }

    var renderLoadErrorRow = function (dataRow, rowNumber) {
        var d = new Date(dataRow.timestamp);
        var currentDate = new Date(Date.now());
        var dateDisplay = d.toDateString();
        if (currentDate.toDateString() === d.toDateString()) {
            dateDisplay = "Today";
        }
        var dHtml = '<div style="margin-bottom: 5px; border-bottom: 2px solid #545454; padding: 5px;">';
        dHtml += '<div style="clear: both;">';
        dHtml += '<div class="column"><span style="font-weight: bold">' + dateDisplay + '</span>, ' + d.toLocaleTimeString('en-US') + '</div>';
        dHtml += '<div class="columnRight cursorPointer accFont" onclick="javascript:$(\'#stackTraceUser_' + rowNumber + '\').toggle();">Stack Trace</div>';
        dHtml += '<div style="clear: both;"></div>';
        dHtml += '</div>';
        dHtml += '<div style="clear: both;" >' + dataRow.message;
        dHtml += '</div>';
        dHtml += '<div style="clear: both;" >URL: ' + dataRow.url;
        dHtml += '</div>';
        dHtml += '<div style="display: none;padding: 5px; background-color: #ffffff;" id="stackTraceUser_' + rowNumber + '">' + window.ePlus.modules.support.formatErrorMessageDetail(dataRow.errorMessageDetail);
        dHtml += '</div>';
        dHtml += '</div>';
        return dHtml;
    }

    var formatErrorMessageDetail = function (errorMessageDetail) {
        if (errorMessageDetail) {
            var headings = ["QUERYSTRING:", "MESSAGE:", "SOURCE:", "TARGETSITE:", "STACKTRACE:", "BROWSER:", "IN:", "FORM:"];
            for (var i = 0; i < headings.length; i++) {
                errorMessageDetail = errorMessageDetail.replace(headings[i], "<br /><br />" + headings[i]);
            }
        }
        return errorMessageDetail;
    }

    var getAllLoadTimesforUser = function (orgId, userId) {
        var maxHoursBack = $("#userLoadDays").html() * 24;
        $("#allLoadTimes").html("Loading...");
        var url = "api/supportTools/reporting/slowLoadTimes?maxHoursBack=" + maxHoursBack + "&count=100000&userId=" + userId + "&orgId=" + orgId + "&minDuration=0";
        $.ajax({
            type: "GET",
            url: url,
            cache: false,
            contentType: "application/json",
            success: function (data) {
                $("#allLoadTimes").html("");
                if (data.length === 0) {
                    $("#userLoadTimeCount").html("No");
                } else {
                    var slowCount = 0;
                    var verySlowCount = 0;
                    for (var i = 0; i < data.length; i++) {
                        if (data[i] != null || data[i] != 'undefined') {
                            var dHtml = window.ePlus.modules.support.renderLoadEventRow(data[i]);
                            $("#allLoadTimes").append(dHtml);
                            var duration = parseInt(data[i].duration / 1000);
                            if (duration >= 30) {
                                verySlowCount += 1;
                            } else if (duration >= 10) {
                                slowCount += 1;
                            }
                        }
                    }
                    $("#allLoadTimes").append('<div id="loadMoreTimes" style="margin-top: 10px; cursor: pointer;" class="textSmall" onclick="javascript:window.ePlus.modules.support.loadMoreLoadTimeDays(\'' + orgId + '\', \'' + userId + '\');">Load More...</div>');
                    $("#userLoadTimeCount").html(data.length);
                    if (data.length > 0) {
                        $("#userLoadSlow").html(parseInt(slowCount / data.length * 100));
                        $("#userLoadVerySlow").html(parseInt(verySlowCount / data.length * 100));
                    }
                }
            }
        });
    }

    var getErrorsforUser = function (orgId, userId, menuItem) {
        var url = "api/supportTools/reporting/appInsightsErrorDetails?maxHoursBack=168&count=1000&userId=" + userId + "&orgId=" + orgId;
        $.ajax({
            type: "GET",
            url: url,
            cache: false,
            contentType: "application/json",
            success: function (data) {
                if (data.length === 0) {
                    $("#errorInstances").append("None");
                } else {
                    for (var i = 0; i < data.length; i++) {
                        if (data[i] != null || data[i] != 'undefined') {
                            var dHtml = window.ePlus.modules.support.renderLoadErrorRow(data[i], i);
                            $("#errorInstances").append(dHtml);
                        }
                    }
                }
                $("#user-modal-" + menuItem + "-count").html(data.length);
                $("#user-modal-" + menuItem + "-count").removeClass("hidden");
            }
        });
    }

    var loadMoreLoadTimeDays = function (orgId, userId) {
        $("#userLoadDays").html($("#userLoadDays").html() * 1 + 30);
        window.ePlus.modules.support.getAllLoadTimesforUser(orgId, userId);
    }

    var getLongLoadTimesforUser = function (orgId, userId, menuItem) {
        var url = "api/supportTools/reporting/slowLoadTimes?maxHoursBack=168&count=1000&userId=" + userId + "&orgId=" + orgId + "&minDuration=10";
        $.ajax({
            type: "GET",
            url: url,
            cache: false,
            contentType: "application/json",
            success: function (data) {
                if (data.length === 0) {
                    $("#slow-load-times").append("None");
                } else {
                    for (var i = 0; i < data.length; i++) {
                        if (data[i] != null || data[i] != 'undefined') {
                            var dHtml = window.ePlus.modules.support.renderLoadEventRow(data[i]);
                            $("#slow-load-times").append(dHtml);
                        }
                    }
                }
                $("#user-modal-" + menuItem + "-count").html(data.length);
                $("#user-modal-" + menuItem + "-count").removeClass("hidden");
            }
        });
    }
    var buildAppInsightsAnalyticsQuery = function (options) {
        if (!options) return '';
        var startDate = new Date(options.timestamp);
        startDate.setMilliseconds(startDate.getMilliseconds() - options.duration);
        var startUtcDate = new Date(new Date(startDate).toUTCString()).toISOString();
        var endUtcDate = new Date(new Date(options.timestamp).toUTCString()).toISOString();
        return 'let startDateTime = todatetime("' + startUtcDate + '");\n' +
            'let endDateTime = todatetime("' + endUtcDate + '");\n' +
            'let Dependencies = dependencies\n' +
            '| where session_Id == "' + options.sessionId + '" and type == "Ajax" and timestamp between(startDateTime..endDateTime);\n' +
            'let Requests = requests\n' +
            '| where session_Id == "' + options.sessionId + '" and timestamp between(startDateTime..endDateTime);\n' +
            'Dependencies\n' +
            '| join kind = leftouter(\n' +
            '       Requests\n' +
            '       | project requestId = id, serverTimestamp = timestamp, requestParentId = operation_ParentId, serverDuration = duration, requestUrl = url, cloud_RoleInstance\n' +
            ') on $left.id == $right.requestParentId\n' +
            '| extend startTimeDuringPageView = timestamp - startDateTime\n' +
            '| project\n' +
            'ajaxTimestamp = timestamp, serverTimestamp, startTimeDuringPageView,\n' +
            'ajaxDuration = duration, serverDuration, difference = round(duration - serverDuration),\n' +
            'requestUrl, data, user_AccountId, user_AuthenticatedId\n' +
            '| order by ajaxDuration';

    }

    var buildAppInsightsRequestDependencyQuery = function (options) {
        if (!options) return '';

        return 'dependencies\n' +
            '| where operation_Id == "' +
            options.operationId +
            '" \n' +
            '| order by duration';
    }

    var buildAppInsightsAnalyticsQueryLink = function (options) {
        if (!options ||
            !options.subscriptionId ||
            !options.resourceGroup ||
            !options.component ||
            !options.query) return null;

        var scope = {
            resources: [
                {
                    resourceId: '/subscriptions/' + encodeURI(options.subscriptionId) +
                        '/resourcegroups/' + encodeURI(options.resourceGroup) +
                        '/providers/microsoft.insights' +
                        '/components/' + options.component
                }
            ]
        };

        var url = 'https://portal.azure.com/#blade/Microsoft_OperationsManagementSuite_Workspace/AnalyticsBlade/initiator' +
            '/AnalyticsShareLinkToQuery/isQueryEditorVisible/true/scope/' +
            encodeURIComponent(JSON.stringify(scope)) + '/query/';

        var query = ePlus.util.compression.compressAndEncodeBase64AndUri(options.query);
        url += query + '/isQueryBase64Compressed/true/timespanInIsoFormat/P1D';

        return url;
    }

    var openInAppInsightsAnalytics = function (operationId, event) {
        event && event.preventDefault();

        var appInsightsLinkUrl = buildAppInsightsAnalyticsQueryLink({
            subscriptionId: this.subscriptionId,
            resourceGroup: this.resourceGroup,
            component: this.component,
            query: buildAppInsightsAnalyticsQuery(operationId)
        });

        window.open(appInsightsLinkUrl);
    }

    var openAppInsightsRequestDependencies = function (operationId, event) {
        event && event.preventDefault();

        var appInsightsLinkUrl = buildAppInsightsAnalyticsQueryLink({
            subscriptionId: this.subscriptionId,
            resourceGroup: this.resourceGroup,
            component: this.component,
            query: buildAppInsightsRequestDependencyQuery(operationId)
        });

        window.open(appInsightsLinkUrl);
    }

    var openAppInsightsEndToEndTransactionTimes = function (eventId, event) {
        event && event.preventDefault();
        
        var dataModel = {
            eventId: eventId
        };
        var componentId = {
            Name: this.component,
            ResourceGroup: this.resourceGroup,
            SubscriptionId: this.subscriptionId
        }
        var url = 'https://portal.azure.com/#blade/AppInsightsExtension/DetailsV2Blade'
            + '/DataModel/' + JSON.stringify(dataModel)
            + '/ComponentId/' + JSON.stringify(componentId);

        window.open(url);
    }

    var loadAccountContacts = function (menuItem, orgId, clientId) {
        $.url = "/GetTreelineControl.aspx?controlName=/uc/support/client_contacts.ascx&orgId=" + orgId + "&clientId=" + clientId;
        $("#account-section-" + menuItem)
            .addClass("progressBackground")
            .load($.url, function () {
                $("#account-section-" + menuItem).removeClass("progressBackground");
                $("#account-modal-" + menuItem + "-count").html($(".accountContact", $("#account-section-" + menuItem)).length);
                $("#account-modal-" + menuItem + "-count").removeClass("hidden");
            });
    }

    var markOrganizationOnboarded = function(completedDate) {
        $("#onboard-details").addClass("hidden");
        $(".onboard-complete").removeClass("hidden");
        $("#onboard-complete-date").html(completedDate);
    }

    var unMarkOrganizationOnboarded = function() {
        $("#onboard-details").removeClass("hidden");
        $(".onboard-complete").addClass("hidden");
    }

    var hideSupportDetailSection = function(detailType) {
        $("." + detailType + "-cell").removeClass("bg-d2d6d8");
        $("#" + detailType + "-orgs-div").addClass("hidden");
    }

    var loadSupportSearch = function(searchType, searchTerm, searchWord, searchPage) {
        if (searchTerm != '') {
            $.url = "/GetTreelineControl.aspx?controlName=/uc/dashboard_v2/widgetItems/modules/" + searchPage + ".ascx&searchType=" + searchType + "&searchTerm=" + escape(searchTerm);
            $("#support-search-results")
                .html("<div class='pad-top-5'>" + searchWord + "</div>")
                .load($.url);
        }
    }

    var loadSupportLibrarySearch = function (searchTerm, orgId, searchWord) {
        if (searchTerm != '' || orgId != '') {
            $.url = "/GetTreelineControl.aspx?controlName=/uc/dashboard_v2/widgetItems/modules/SupportLibrarySearchResults.ascx&orgId=" + orgId + "&searchTerm=" + escape(searchTerm);
            $("#support-search-results")
                .html("<div class='pad-top-5'>" + searchWord + "</div>")
                .load($.url);
        }
    }

    var generateEdelweissLoginEmail = function (orgId, userId) {
        $.post("/getJSONData.aspx?m=User&builder=GenerateEdelweissLoginEmail", { OrgID: orgId, UserID: userId },
            function (data) {
                if (data.code == "SUCCESS") {
                    alert(data.text);
                }
                else {
                    alert(data.text);
                }
            }, "json");
    }

    var removeUserFromAccount = function (userId, orgId, callback) {
        if (confirm("Clicking 'OK' will remove all account access for this user. The user's personal, non-account specific data will transfer to a new account.")) {
            $.post("/getJSONData.aspx?m=User&builder=RemoveUserFromAccount",
                {
                    orgID: orgId,
                    userID: userId,
                },
                function (data) {
                    if (typeof callback === 'function') {
                        callback(data);
                    }
                }, "json");
        }
    };

    var removeUserWithOrgId = function (orgId, userId) {
        if (confirm('Are you sure you want to delete this user?!')) {
            $.post("/getJSONData.aspx?builder=RemoveUser", { orgID: orgId, userID: userId },
                function (data) {
                    if (data.code == "SUCCESS") {
                        alert(data.text);
                        window.location.reload(true);
                    }
                    else {
                        alert(data.text);
                    }
                }, "json");
        }
    }

    var saveUserToRecentlyViewed = function (appUserID, source) {
        jQuery.post("/GetJSONData.aspx?m=Alpha&builder=SaveRecentlyViewed_Support", { appUserID: appUserID },
            function (data) {
                if (data.code == "SUCCESS") {
                    if (source == "login") {
                        window.location = '/admin/AdminLoginTreelineUser.aspx?email=' + data.data + '&source=fromadmin'
                    } else if (source == 'homepage') {
                        window.ePlus.modules.dashboard.refreshWidgetsWithResultType(getEnumValue("resultType", "SUPPORTUSERLOGIN"));
                    }
                }
                else {
                    alert("An error occurred while saving this user to your recently viewed list.  If this persists, tell John!");
                }
            }, "json");
    }

    var removeLibraryLocationCode = function (row, storeId, libId, menuItem, orgId) {
        var url = "api/supportTools/libraryLocation?storeId=" + storeId + "&libId=" + libId;
        $.ajax({
            type: "DELETE",
            url: url,
            cache: false,
            contentType: "application/json"
        }).done(function () {
            window.ePlus.modules.support.reloadLocationAssignment(menuItem, orgId);
        });
    }

    var assignLibraryLocationCode = function (storeId, libId, menuItem, orgId) {
        var url = "api/supportTools/libraryLocation?storeId=" + storeId + "&libId=" + libId;
        $.ajax({
            type: "PUT",
            url: url,
            cache: false,
            contentType: "application/json"
        }).done(function () {
            if (libId === '') {
                window.ePlus.modules.support.reloadLocationAssignment(menuItem, orgId);
            }
        });
    }

    var reloadLocationAssignment = function (menuItem, orgId) {
        window.ePlus.modules.support.loadAnalyticsLocations(menuItem, orgId);
    }

    var refreshAccountUserCount = function (orgId, accountSupportView, supportAccountMenu) {
        loadAccountPeopleScroller(orgId, accountSupportView);
        getAccountUserCount(supportAccountMenu, orgId);
        window.ePlus.modules.dashboard.refreshWidgetsWithResultType(getEnumValue("resultType", "SUPPORTACCOUNTS"));
    }

    var openLibraryMap = function (row, serviceType, viewId, laneId) {
        var url = "/GetTreelineControl.aspx?controlName=/uc/organization/libraryMap.ascx&row=" + row + "&groupId=" + viewId + "&serviceType=" + serviceType + "&laneId=" + laneId;
        openMultiModal({
            id: 'library-map',
            url: url,
            width: "90%",
            height: "90%"
        });
    }

    var toggleAccountGroupAccess = function (doGrantAccess, orgId, groupId, confirmMessage) {
        if (!doGrantAccess && !confirm(confirmMessage)) {
            return false;
        }

        ePlus.modules.org.toggleOrgGroupAccess(doGrantAccess, orgId, groupId, function () {
            closeModal();
            openAccountSupport(orgId);
        });

        return true;
    };

    var toggleUserGroupAccess = function (doGrantAccess, appUserId, groupId) {
        ePlus.user.toggleUserGroupAccess(doGrantAccess, appUserId, groupId);
        return true;
    };

    var removeLibraryCode = function (orgId, fscsKey) {
        if (confirm("Are you sure you to remove this assignment?")) {
            var url = "api/supportTools/library?orgId=" + orgId + "&fscsKey=" + fscsKey;
            $.ajax({
                type: "DELETE",
                url: url,
                cache: false,
                contentType: "application/json",
                complete: function () {
                    closeModal();
                    openAccountSupport(orgId);
                }
            });
        }
    }

    var removeRelationship = function (relatedOrgId, thisOrgId, relationship) {
        var supplierOrgId;
        var clientOrgId;
        if (relationship === 0) {
            supplierOrgId = thisOrgId;
            clientOrgId = relatedOrgId;
        } else {
            supplierOrgId = relatedOrgId;
            clientOrgId = thisOrgId;
        }

        var url = 'api/supportTools/publishers/' + supplierOrgId + '/relationships/' + clientOrgId;
        $.ajax({
            type: "DELETE",
            url: url,
            cache: false,
            contentType: "application/json"
        })
            .done(function (data) {
                loadPublisherRelationships(getEnumValue('supportAccountMenu', 'RELATIONSHIPS'), thisOrgId);
            });
    }

    var addNewAdvocacy = function () {
        var records = $('#user-advocacy-record').length;
        $('#user-advocacy-container').append('<div id="added-record-' + (records + 1) + '"></div>');
        $.url = "/GetTreelineControl.aspx?controlName=/uc/support/advocacy/userAdvocacyRecord.ascx";
        $('#added-record-' + (records + 1)).load($.url);
        $('#add-new-advocacy').addClass('hidden');
    }

    var deleteSupportFollowUp = function (appUserId, orgId) {
        var followup = {
            appUserId: appUserId,
            targetOrgId: orgId
        };

        $.ajax({
            type: 'DELETE',
            url: 'api/supportTools/orgs/' + orgId + '/followup',
            dataType: 'json',
            contentType: 'application/json; charset=utf-8',
            data: JSON.stringify(followup)
        })
            .done(function (data) {
                getSupportFollowUp(orgId);
                window.ePlus.modules.dashboard.refreshWidgetsWithResultType(getEnumValue("resultType", "SUPPORTFOLLOWUPS"));
            });
    }

    var saveSupportFollowUp = function (appUserId, followUpDate, orgId) {
        var followup = {
            appUserId: appUserId,
            followUpDate: followUpDate,
            targetOrgId: orgId
        };

        $.ajax({
            type: 'PUT',
            url: 'api/supportTools/orgs/' + orgId + '/followup',
            dataType: 'json',
            contentType: 'application/json; charset=utf-8',
            data: JSON.stringify(followup)
        })
            .done(function (data) {
                getSupportFollowUp(orgId, appUserId);
                window.ePlus.modules.dashboard.refreshWidgetsWithResultType(getEnumValue("resultType", "SUPPORTFOLLOWUPS"));
        });
    }

    var getSupportFollowUp = function (orgId, appUserId) {
        $.ajax({
            type: 'GET',
            url: 'api/supportTools/orgs/' + orgId + '/followup',
            cache: false,
            contentType: "application/json"
        })
            .done(function (data) {
                $('#follow-up-text')
                    .html(data)
                    .removeClass('hidden');
                $('#follow-up-input').addClass('hidden');

                $("#follow-up-link").on('click', function () {
                    $('#follow-up-input')
                        .removeClass('hidden')
                        .datepicker("show");
                    $("#follow-up-link").addClass('hidden')
                }); 

                $('.follow-up-delete', $('#account-follow-up')).on('click', function () {
                    deleteSupportFollowUp(appUserId, orgId)
                });
            });
    }

    var saveOrgNote = function (orgId) {
        var $editor;

        try {
            $editor = CKEDITOR.instances["inline-org-note"];
        } catch (e) { }

        if (!$editor) {
            alert("The Editor Did Not Load Correctly");
            return;
        }

        var note = $editor.getData();

        $.ajax({
            type: 'PUT',
            url: 'api/supportTools/orgs/' + orgId + '/note',
            data: { '': note }
        }).done(function () {
            alert(window.getRes('saved'));
        });
    }

    var setOrganizationPreference = function (orgId, prefType, prefName, value) {
        var url = 'api/organizations/' + orgId + '/preferences/' + prefType + '/' + prefName + '/' + value;

        $.ajax({
            type: 'POST',
            cache: false,
            url: url,
            contentType: 'application/json'
        });
    }
    
    return {
        init: init,
        loadAccountNotes: loadAccountNotes,
        loadUserNotes: loadUserNotes,
        loadAccountNps: loadAccountNps,
        loadUserNps: loadUserNps,
        getAccountUserCount: getAccountUserCount,
        getAccountNpsScore: getAccountNpsScore,
        loadAccountBilling: loadAccountBilling,
        getUserNpsScore: getUserNpsScore,
        loadAccountPeopleScroller: loadAccountPeopleScroller,
        loadAccountBillingProfile: loadAccountBillingProfile,
        loadUserSupportProfile: loadUserSupportProfile,
        loadUserSupportIFrame: loadUserSupportIFrame,
        loadAccountIFrame: loadAccountIFrame,
        deleteNote: deleteNote,
        updateNote: updateNote,
        createNote: createNote,
        getActiveClientAutoComplete: getActiveClientAutoComplete,
        supportGrabNps: supportGrabNps,
        toggleSupportWidgets: toggleSupportWidgets,
        openFreshdeskAccount: openFreshdeskAccount,
        openFreshdeskTicket: openFreshdeskTicket,
        checkSupportWidgetVisibility: checkSupportWidgetVisibility,
        getFreshdeskStatus: getFreshdeskStatus,
        readPageParameter: readPageParameter,
        getPageDescriptionFromUrl: getPageDescriptionFromUrl,
        renderLoadEventRow: renderLoadEventRow,
        redirectToPage: redirectToPage,
        getDurationBackgroundColor: getDurationBackgroundColor,
        renderLoadErrorRow: renderLoadErrorRow,
        formatErrorMessageDetail: formatErrorMessageDetail,
        getErrorsforUser: getErrorsforUser,
        getAllLoadTimesforUser: getAllLoadTimesforUser,
        loadMoreLoadTimeDays: loadMoreLoadTimeDays,
        getLongLoadTimesforUser: getLongLoadTimesforUser,
        removeSingleQuotes: removeSingleQuotes,
        getAppInsightsAnalyticsQueryLink: buildAppInsightsAnalyticsQueryLink,
        openInAppInsightsAnalytics: openInAppInsightsAnalytics,
        loadAccountContacts: loadAccountContacts,
        loadAccountAnalytics: loadAccountAnalytics,
        loadAnalyticsLocations: loadAnalyticsLocations,
        loadAccountAnalyticsUsage: loadAccountAnalyticsUsage,
        markOrganizationOnboarded: markOrganizationOnboarded,
        unMarkOrganizationOnboarded: unMarkOrganizationOnboarded,
        hideSupportDetailSection: hideSupportDetailSection,
        openAppInsightsRequestDependencies: openAppInsightsRequestDependencies,
        openAppInsightsEndToEndTransactionTimes: openAppInsightsEndToEndTransactionTimes,
        loadSupportSearch: loadSupportSearch,
        loadSupportLibrarySearch: loadSupportLibrarySearch,
        generateEdelweissLoginEmail: generateEdelweissLoginEmail,
        removeUserWithOrgId: removeUserWithOrgId,
        saveUserToRecentlyViewed: saveUserToRecentlyViewed,
        loadConsortiumMembers: loadConsortiumMembers,
        removeLibraryLocationCode: removeLibraryLocationCode,
        assignLibraryLocationCode: assignLibraryLocationCode,
        reloadLocationAssignment: reloadLocationAssignment,
        refreshAccountUserCount: refreshAccountUserCount,
        openLibraryMap: openLibraryMap,
        loadPublisherRelationships: loadPublisherRelationships,
        loadPublisherPromotions: loadPublisherPromotions,
        loadAccountAdvocacy: loadAccountAdvocacy,
        loadDrcProfiles: loadDrcProfiles,
        loadImprintsInCatalogs: loadImprintsInCatalogs,
        toggleAccountGroupAccess: toggleAccountGroupAccess,
        toggleUserGroupAccess: toggleUserGroupAccess,
        removeLibraryCode: removeLibraryCode,
        removeRelationship: removeRelationship,
        addNewAdvocacy: addNewAdvocacy,
        deleteSupportFollowUp: deleteSupportFollowUp,
        saveSupportFollowUp: saveSupportFollowUp,
        getSupportFollowUp: getSupportFollowUp,
        grabTitleOwnershipClaim: grabTitleOwnershipClaim,
        putBackTitleOwnershipClaim: putBackTitleOwnershipClaim,
        markTitleOwnershipClaimAsProcessed: markTitleOwnershipClaimAsProcessed,
        saveCommentOnTitleOwnershipClaim: saveCommentOnTitleOwnershipClaim,
        saveOrgNote: saveOrgNote,
        removeUserFromAccount: removeUserFromAccount,
        setUserPreference: setUserPreference,
        setOrganizationPreference: setOrganizationPreference
    }
})();;
; window.ePlus.modules.support.accountModal = (function () {
    var support = window.ePlus.modules.support;
    var settings = {};

    var initializeIndie360AccessCheckbox = function () {
        $('#indie360-service').on('click', function (e) {
            var doGrantAccess = this.checked;
            var orgId = settings.orgId;
            var groupId = settings.groups.indie360Service;
            var confirmMessage = settings.localizations.removeIndie360Service;

            if (!support.toggleAccountGroupAccess(doGrantAccess, orgId, groupId, confirmMessage)) {
                e.preventDefault();
            }
        });
    };

    var initializeUI = function () {
        initializeIndie360AccessCheckbox();
    }

    var initialize = function (options) {
        settings = options;
        initializeUI();
    };

    return {
        initialize: initialize,
    };
})();;
window.ePlus.modules.support = window.ePlus.modules.support || {};
window.ePlus.modules.support.storedProcedures = (function () {
    var initialize = function() {
        showSelectedParameterSection();

        $('#support-stored-procedure').on('change', function () {
            showSelectedParameterSection();
        });

        $('#support-stored-procedure-submit').on('click', function () {
            executeSelectedStoredProcedure();
        });

        $.datepicker.setDefaults($.datepicker.regional['']);

        $('.support-stored-procedure-parameter-date').datepicker();
    };

    var getParameters = function() {
        var storedProcedureId = getSelectedStoredProcedureId();
        var $inputs = $('.support-stored-procedure-parameter-' + storedProcedureId);
        var parameters = [];

        $inputs.each(function () {
            var $input = $(this);
            var parameterName = $input.data('parameter-name');
            var value = $input.val();

            if ($input.attr('type') === 'checkbox') {
                value = $input.is(':checked');
            }

            parameters.push({
                name: parameterName,
                value: value
            });
        });

        return parameters;
    }

    var executeSelectedStoredProcedure = function() {
        var storedProcedureId = getSelectedStoredProcedureId();
        var parameters = getParameters();
        var parameterQueries = [];

        for (var i in parameters) {
            var parameter = parameters[i];
            var value = parameter.value;

            if (value == null || value == '') {
                alert("enter a value for " + parameter.name);

                return;
            }

            parameterQueries.push('parameters[' + i + '].key=' + parameter.name + '&parameters[' + i + '].value=' + value);
        }

        var url = 'api/supportTools/storedProcedure/' + storedProcedureId + '/execute';

        if (parameterQueries) {
            url += '?';
            url += parameterQueries.join('&');
        }

        window.open(encodeURI(url));
    }

    var getSelectedStoredProcedureId = function() {
        return $('#support-stored-procedure :selected').val();
    }

    var showSelectedParameterSection = function() {
        var storedProcedureId = getSelectedStoredProcedureId();

        hideAllParameterSection();
        showParameterSection(storedProcedureId);
    }

    var hideAllParameterSection = function() {
        $('.support-stored-procedure-parameters').each(function () {
            $(this).addClass('hidden');
        })
    }

    var showParameterSection = function(storedProcedureId) {
        $('#support-stored-procedure-parameters-' + storedProcedureId).removeClass('hidden');
    }

    return {
        initialize: initialize
    };
})();;
window.ePlus.modules.support = window.ePlus.modules.support || {};
window.ePlus.modules.support.onixToolTips = (function () {
    var tooltip_data = [
        { "name": "Imprint", "selector": "div.headerImprint", "data": [{ "name": "ONIX 2.1", "description": "/product/imprint/ImprintName" }, { "name": "ONIX 3.0", "description": "/product/publishingdetail/imprint/imprintname" }, { "name": "Export File and Stored Procedure", "description": "Title.txt[5] Import_ONIX_All_Title;Imprint.txt Import_ONIX_Imprint" }, { "name": "Database Table", "description": "TreelineUW.dbo.BookAttributes[Im+C2:F48print];TreelineUW.dbo.ISBNMaster[PubName]" },] },
        { "name": "Date Added", "selector": "div.headerDateAdded", "data": [{ "name": "Database Table", "description": "CatalogManagement.dbo.Catalog_Product [CreatedDate]" },] },
        { "name": "Publisher", "selector": "div.headerPublisher", "data": [{ "name": "General Comments", "description": "This is the publisher that has most recently added this title to a catalog;unless viewing this title in a catalog, in which case it will always be the publisher of the current catalog." },] },
        { "name": "Catalog Page", "selector": "div.catalogPdfPage", "data": [{ "name": "Database Table", "description": "CatalogManagement.dbo.Catalog_Product [CatalogPagePdf]" },] },
        { "name": "Insert", "selector": "div.insert-image-con", "data": [] },
        { "name": "Burst", "selector": "div.pve_burst", "data": [{ "name": "Database Table", "description": "CatalogManagement.dbo.Organization_Product_Attribute;  where AttributeID = 59" }, { "name": "General Comments", "description": "Work currently being done to accept these via ONIX 3.0.;No ONIX 2.1 support currently planned;Ancillary_Feed: Bursts" },] },
        { "name": "Jacket Cover", "selector": "div.listView_jacketCoverCheckmark", "data": [{ "name": "Database Table", "description": "CatalogManagement.dbo.Product_Image;  where Type = 1" }, { "name": "General Comments", "description": "Images loaded via FTP ; Default location /edelweiss/images/jacket_covers" },] },
        { "name": "Title", "selector": "span.textLarge", "data": [{ "name": "ONIX 2.1", "description": "/product/title" }, { "name": "ONIX 3.0", "description": "/product/descriptivedetail/titledetail" }, { "name": "Export File and Stored Procedure", "description": "Title.txt[2] Import_ONIX_All_Title" }, { "name": "Database Table", "description": "TreelineUW.dbo.BookAttributes [Title];TreelineUW.dbo.ISBNMaster [Title]" },] },
        { "name": "Subtitle", "selector": "span.pve_subName", "data": [{ "name": "ONIX 2.1", "description": "/product/title/Subtitle (b029)" }, { "name": "ONIX 3.0", "description": "/product/descriptivedetail/titledetail/titleelement/subtitle" }, { "name": "Export File and Stored Procedure", "description": "Title.txt[19] Import_ONIX_All_Title" }, { "name": "Database Table", "description": "TreelineUW.dbo.BookAttributes[Subtitle];TreelineUW.dbo.ISBNMaster [Subtitle]" },] },
        { "name": "Edition", "selector": "span.pve_edition", "data": [{ "name": "ONIX 2.1", "description": "/product/EditionTypeCode (b056);/product/EditionNumber (b057)" }, { "name": "ONIX 3.0", "description": "/product/descriptivedetail/editiontype;/product/descriptivedetail/editionnumber" }, { "name": "Export File and Stored Procedure", "description": "Title.txt[38][22] Import_ONIX_All_Title;EditionType.txt Import_ONIX_EditionType" }, { "name": "Database Table", "description": "TreelineUW.dbo.BookAttributes [EditionType][Edition];CatalogManagement.dbo.EditionType" },] },
        { "name": "Author", "selector": "div.pve_contributor", "data": [{ "name": "ONIX 2.1", "description": "/product/contributor" }, { "name": "ONIX 3.0", "description": "/product/descriptivedetail/contributor" }, { "name": "Export File and Stored Procedure", "description": "Contrib.txt Import_ONIX_Contrib;Title.txt[4] Import_ONIX_All_Title" }, { "name": "Database Table", "description": "CatalogManagement.dbo.Product_Contributor;TreelineUW.dbo.ISBNMaster [MultContrib][Author]" }, { "name": "General Comments", "description": "Ancillary Feed: Contributor" },] },
        { "name": "Title Type", "selector": "div.product-view>div.textMedium>ul.hidden:has(li.pve_shipDate)+div>b:contains('LIST')", "data": [{ "name": "General Comments", "description": "This is based purely on the pub date." },] },
        { "name": "Pub Date/Ship Date", "selector": "div.product-view>div.textMedium>ul.hidden:has(li.pve_shipDate)+div", "data": [{ "name": "ONIX 2.1", "description": "/product/PublicationDate (b003);/product/supplydetail/ExpectedShipDate (j142)" }, { "name": "Export File and Stored Procedure", "description": "Title.txt[06][21] Import_ONIX_All_Title;" }, { "name": "Database Table", "description": "TreelineUW.dbo.BookAttributes [PubDate][ShipDate];TreelineUW.dbo.ISBNMaster [PubDate][ShipDate]" },] },
        { "name": "On Sale Date", "selector": "div.product-view>div.textMedium>ul.hidden:has(li.pve_shipDate)+div>span", "data": [{ "name": "ONIX 2.1", "description": "/product/supplydetail/OnSaleDate (j142)" }, { "name": "ONIX 3.0", "description": "/product/publishingdetail/publishingdate;/product/productsupply/supplydetail/supplydate  (role 08)" }, { "name": "Export File and Stored Procedure", "description": "Title.txt[07] Import_ONIX_All_Title;" }, { "name": "Database Table", "description": "TreelineUW.dbo.BookAttributes [StreetDate];TreelineUW.dbo.ISBNMaster [StreetDate]" },] },
        { "name": "SKU", "selector": "div.pve_sku", "data": [{ "name": "ONIX 2.1", "description": "/product/productidentifier" }, { "name": "ONIX 3.0", "description": "/product/productidentifier" }, { "name": "Database Table", "description": "TreelineUW.dbo.BookAttributes [SKU];TreelineUW.dbo.ISBNMaster [ISBN]" },] },
        { "name": "Format", "selector": "span.pve_format", "data": [{ "name": "ONIX 2.1", "description": "/product/ProductForm (b012);/product/ProductFormDetail (b333)" }, { "name": "ONIX 3.0", "description": "/product/descriptivedetail/productform;/product/descriptivedetail/productformdescription" }, { "name": "Export File and Stored Procedure", "description": "Title.txt[10][20] Import_ONIX_All_Title" }, { "name": "Database Table", "description": "TreelineUW.dbo.BookAttributes [FormatCode]" },] },
        { "name": "List Price", "selector": "div.pve_listPrice", "data": [{ "name": "ONIX 2.1", "description": "/product/supplydetail/price" }, { "name": "ONIX 3.0", "description": "/product/productsupply/supplydetail/price" }, { "name": "Export File and Stored Procedure", "description": "Price.txt Import_ONIX_Price;Title.txt[09][17][18][33] Import_ONIX_All_Title" }, { "name": "Database Table", "description": "TreelineUW.dbo.BookAttributes [ListPrice];TreelineUW.dbo.ISBNMaster [ListPrice]" }, { "name": "General Comments", "description": "Prices are stored in BookAttributes under the TargetGroupID of the Currency, not the Market.;Ancillary Feed: Price" },] },
        { "name": "Discount Code", "selector": "div.pve_discountcode", "data": [{ "name": "ONIX 2.1", "description": "/product/supplydetail/price/discountcoded" }, { "name": "ONIX 3.0", "description": "/product/productsupply/supplydetail/price/discountcoded" }, { "name": "Export File and Stored Procedure", "description": "Title.txt[23] Import_ONIX_All_Title;Price.txt[7] Import_ONIX_Price" }, { "name": "Database Table", "description": "TreelineUW.dbo.BookAttributes [Proprietary_DiscountCode]" },] },
        { "name": "Categories", "selector": "div.pve_categories", "data": [{ "name": "ONIX 2.1", "description": "/product/subject,/product/BASICMainSubject (b064);/product/BICMainSubject (b065)" }, { "name": "ONIX 3.0", "description": "/product/descriptivedetail/subject" }, { "name": "Export File and Stored Procedure", "description": "Category.txt Import_ONIX_Category;Title.txt[8] Import_ONIX_All_Title" }, { "name": "Database Table", "description": "CatalogManagement.dbo.Product_Category;TreelineUW.dbo.ISBNMaster [BISAC1]" },] },
        { "name": "Age Range", "selector": "div.pve_ageRange", "data": [{ "name": "ONIX 2.1", "description": "/product/audiencerange" }, { "name": "ONIX 3.0", "description": "/product/descriptivedetail/audiencerange" }, { "name": "Export File and Stored Procedure", "description": "Audience.txt Import_ONIX_Audience" }, { "name": "Database Table", "description": "CatalogManagement.dbo.Product_AgeRange" },] },
        { "name": "Dynamic Attributes", "selector": "div.pve_dynamicAttributes", "data": [{ "name": "Database Table", "description": "CatalogManagement.dbo.Organization_Product_Attribute" }, { "name": "General Comments", "description": "These are usually custom data elements set up for particular publishers." },] },
        { "name": "Number of Pages/Print Run", "selector": "ul.hidden:has(li.pve_numberOfPages)+div:contains('pages')", "data": [{ "name": "ONIX 2.1", "description": "/product/extent;/product/NumberOfPages (b061);/product/InitialPrintRun (k167)" }, { "name": "ONIX 3.0", "description": "/product/descriptivedetail/extent;/product/contentdetail/contentitem/textitem/numberofpages;/product/productsupply/marketpublishingdetail/initialprintrun" }, { "name": "Export File and Stored Procedure", "description": "Title.txt[12] Import_ONIX_All_Title" }, { "name": "Database Table", "description": "TreelineUW.dbo.ISBNMaster [Pages]" }, { "name": "General Comments", "description": "Ancillary Feed: Product_PrintRun" },] },
        { "name": "Series", "selector": "div.pve_series", "data": [{ "name": "ONIX 2.1", "description": "/product/series/TitleOfSeries (b018)" }, { "name": "ONIX 3.0", "description": "/product/descriptivedetail/collection/titledetail/titleelement/titletext" }, { "name": "Export File and Stored Procedure", "description": "Title.txt[11] Import_ONIX_All_Title" }, { "name": "Database Table", "description": "TreelineUW.dbo.BookAttributes [Series];TreelineUW.dbo.ISBNMaster [Series]" },] },
        { "name": "Series Number", "selector": "span.pve_seriesNumber", "data": [{ "name": "ONIX 2.1", "description": "/product/series/NumberWithinSeries (b019)" }, { "name": "ONIX 3.0", "description": "/product/descriptivedetail/collection/titledetail/titleelement/partnumber" }, { "name": "Export File and Stored Procedure", "description": "Title.txt[40] Import_ONIX_All_Title" }, { "name": "Database Table", "description": "TreelineUW.dbo.BookAttributes [SeriesNumber]" },] },
        { "name": "Illustration Note", "selector": "div.pve_illustrationNote", "data": [{ "name": "ONIX 2.1", "description": "/product/IllustrationsNote (b062);/product/NumberOfIllustrations (b125)" }, { "name": "ONIX 3.0", "description": "/product/descriptivedetail/illustrationsnote;/product/descriptivedetail/numberofilllustrations" }, { "name": "Export File and Stored Procedure", "description": "Title.txt[28][27] Import_ONIX_All_Title" }, { "name": "Database Table", "description": "TreelineUW.dbo.BookAttributes [IllustrationNote][NumberOfIllustrations]" }, { "name": "General Comments", "description": "When an IllustrationNote isn't supplied, we fall back on using the NumberOfIllustrations element, and creating an IllustrationNote in the format 'X Illustrations'" },] },
        { "name": "Measure", "selector": "div.pve_measure", "data": [{ "name": "ONIX 2.1", "description": "/product/measure" }, { "name": "ONIX 3.0", "description": "/product/descriptivedetail/measure" }, { "name": "Export File and Stored Procedure", "description": "Measure.txt Import_ONIX_Measure;Title.txt[14] Import_ONIX_All_Title" }, { "name": "Database Table", "description": "CatalogManagement.dbo.Product_Measure;TreelineUW.dbo.ISBNMaster [Measurements]" }, { "name": "General Comments", "description": "The data for the site is read from ISBNMaster, and is automatically converted, but that data is created from ;the Product_Measure table in the TreelineTools job 'update-product-measurements'. ;The 'Product_Measure table is where the ONIX data is ingested." },] },
        { "name": "Publishing Status", "selector": "div.pve_publishingStatus", "data": [{ "name": "ONIX 2.1", "description": "/product/PublishingStatus (b394)" }, { "name": "ONIX 3.0", "description": "/product/publishingdetail/publishingstatus" }, { "name": "Export File and Stored Procedure", "description": "Title.txt[25] Import_ONIX_All_Title" }, { "name": "Database Table", "description": "TreelineUW.dbo.BookAttributes [PublishingStatus]" },] },
        { "name": "Sales Rights", "selector": "div.pve_salesRights", "data": [{ "name": "ONIX 2.1", "description": "/product/salesrestriction/SalesRestrictionDetail (b383)" }, { "name": "ONIX 3.0", "description": "/product/publishingdetail/salesrestriction/salerestrictionnote" }, { "name": "Export File and Stored Procedure", "description": "Title.txt[31] Import_ONIX_All_Title" }, { "name": "Database Table", "description": "TreelineUW.dbo.BookAttributes [SalesRights]" }, { "name": "General Comments", "description": "This is a free text field, and different to the list of countries you get when you click this link. ;Defaults to 'View' when no data is present." },] },
        { "name": "Awards", "selector": "div.pve_awards", "data": [{ "name": "Database Table", "description": "CatalogManagement.dbo.Product_Award" }, { "name": "General Comments", "description": "These are manually updated." },] },
        { "name": "Carton Quantity", "selector": "div.pve_cartonQtyNoZero", "data": [{ "name": "ONIX 2.1", "description": "/product/NumberOfPieces (b210)" }, { "name": "ONIX 3.0", "description": "/product/descriptivedetail/productpart/numberofcopies" }, { "name": "Export File and Stored Procedure", "description": "Title.txt[13] Import_ONIX_All_Title" }, { "name": "Database Table", "description": "TreelineUW.dbo.BookAttributes [CtnQty];TreelineUW.dbo.ISBNMaster [CtnQty]" },] },
        { "name": "Personal Tags", "selector": "ul.personalTag", "data": [] },
        { "name": "Colleague Tags", "selector": "div.colleague-tag-container", "data": [] },
        { "name": "Excerpt", "selector": "div.titleview-excerpts", "data": [{ "name": "ONIX 2.1", "description": "/product/othertext;  Default Mapping: Codelist 33 Type 23 (Excerpt from book)" }, { "name": "ONIX 3.0", "description": "/product/collateraldedail/textcontent" }, { "name": "Export File and Stored Procedure", "description": "TextContent.txt Import_ONIX_TextContent" }, { "name": "Database Table", "description": "CatalogManagement.dbo.Product_Description;  where Description_Type = 3" }, { "name": "General Comments", "description": "Check org/config specific mapping in CatalogProcessing.dbo.ONIXTextMapping tables;Ancillary Feed: Product_Description" },] },
        { "name": "Illustrations", "selector": "div.illustrations-container", "data": [{ "name": "Database Table", "description": "CatalogManagement.dbo.Product_Image;  where Type = 2" },] },
        { "name": "Comps", "selector": "div.titleCompsDiv", "data": [{ "name": "ONIX 2.1", "description": "/product/relatedproduct" }, { "name": "ONIX 3.0", "description": "/product/relatedmaterial/relatedproduct/" }, { "name": "Export File and Stored Procedure", "description": "RelatedProduct.txt Import_ONIX_RelatedProduct" }, { "name": "Database Table", "description": "CatalogManagement.dbo.Product_Comparable_Instances" }, { "name": "General Comments", "description": "Comps have to be manually configured for each ConfigID in ;the CatalogProcessing.dbo.ONIXRelatedProductConfig table.;Ancillary Feed: Related_Products" },] },
        { "name": "Summary", "selector": "div[id^='desc_summary']", "data": [{ "name": "ONIX 2.1", "description": "/product/othertext;;  Default Mapping (Keynote):;    CodeList 33 TypeCode 02 (Short Description/annotation);; Default Mappings (Long Description):;    Codelist 33 TypeCode 01 (Main Description);    CodeList 33 TypeCode 03 (Long Description)" }, { "name": "ONIX 3.0", "description": "/product/collateraldedail/textcontent" }, { "name": "Export File and Stored Procedure", "description": "TextContent.txt Import_ONIX_TextContent" }, { "name": "Database Table", "description": "CatalogManagement.dbo.Product_Description;  where Description_Type = 6 (Keynote);  where Description_Type = 1 (Long Description)" }, { "name": "General Comments", "description": "This field is a concatenation of the 'Keynote' and 'Long Description' descriptions.;Ancillary Feed: Product_Description" },] },
        { "name": "Contributor Bio", "selector": "div[id^='desc_contributorbio']", "data": [{ "name": "ONIX 2.1", "description": "/product/contributor/BiographicalNote;/product/othertext;  Default Mapping: CodeList 33 TypeCode 13 (Biographical Note)" }, { "name": "ONIX 3.0", "description": "/product/collateraldedail/textcontent" }, { "name": "Export File and Stored Procedure", "description": "TextContent.txt Import_ONIX_TextContent;BioNote.txt Import_ONIX_OtherText" }, { "name": "Database Table", "description": "CatalogManagement.dbo.Product_Description;  where Description_Type = 4" }, { "name": "General Comments", "description": "Author Bios can come in via both the 'Contributor' and 'OtherText' elements, but only one will win.;Make sure that both aren't configured and being sent.;Ancillary Feed: Product_Description" },] },
        { "name": "Author Image", "selector": "div[id^='authorImages']", "data": [{ "name": "General Comments", "description": "Images loaded via FTP ; Default location /edelweiss/images/author_images" },] },
        { "name": "Links", "selector": "div.productLink", "data": [{ "name": "ONIX 2.1", "description": "/product/productwebsite" }, { "name": "ONIX 3.0", "description": "/product/collateraldetail/supportingresource (where resourcemode = 06)" }, { "name": "Export File and Stored Procedure", "description": "ProductWebsite.txt Import_ONIX_ProductWebsite" }, { "name": "General Comments", "description": "Ancillary Feed: Product_Links" },] },
        { "name": "Marketing Plans", "selector": "div[id^='desc_marketing_plans']", "data": [{ "name": "ONIX 2.1", "description": "/product/othertext;  Default Mapping: Codelist 33 Type 26 (Description for press)" }, { "name": "ONIX 3.0", "description": "/product/collateraldedail/textcontent" }, { "name": "Export File and Stored Procedure", "description": "TextContent.txt Import_ONIX_TextContent" }, { "name": "Database Table", "description": "CatalogManagement.dbo.Product_Description;  where Description_Type = 9" }, { "name": "General Comments", "description": "Check org/config specific mapping in CatalogProcessing.dbo.ONIXTextMapping tables;Ancillary Feed: Product_Description" },] },
        { "name": "Key Selling Points", "selector": "div[id^='desc_ksp']", "data": [{ "name": "ONIX 2.1", "description": "/product/othertext;  Default Mapping: Codelist 33 TypeCode 19 (Feature)" }, { "name": "ONIX 3.0", "description": "/product/collateraldedail/textcontent" }, { "name": "Export File and Stored Procedure", "description": "TextContent.txt Import_ONIX_TextContent" }, { "name": "Database Table", "description": "CatalogManagement.dbo.Product_Description;  where Description_Type = 7" }, { "name": "General Comments", "description": "Check org/config specific mapping in CatalogProcessing.dbo.ONIXTextMapping tables;Ancillary Feed: Product_Description" },] },
        { "name": "Quotes", "selector": "div.[id^='desc_quotes_reviews']", "data": [{ "name": "ONIX 2.1", "description": "/product/othertext; Default Mappings:;    CodeList 33 Type 07 (Review Text);    CodeList 33 TypeCode 08 (Review Quote);    CodeList 33 TypeCode 10 (Previous Review Quote)" }, { "name": "ONIX 3.0", "description": "/product/collateraldedail/textcontent" }, { "name": "Export File and Stored Procedure", "description": "TextContent.txt Import_ONIX_TextContent" }, { "name": "Database Table", "description": "CatalogManagement.dbo.Product_Description;  where Description_Type = 2" }, { "name": "General Comments", "description": "Check org/config specific mapping in CatalogProcessing.dbo.ONIXTextMapping tables;Ancillary Feed: Product_Description" },] },
        { "name": "Unpublished Endorsements", "selector": "div.[id^='desc_endorsements']", "data": [{ "name": "ONIX 2.1", "description": "/product/othertext;  Default Mapping: CodeList 33 TypeCode 30 (Unpublished Endorsement)" }, { "name": "ONIX 3.0", "description": "/product/collateraldedail/textcontent" }, { "name": "Export File and Stored Procedure", "description": "TextContent.txt Import_ONIX_TextContent" }, { "name": "Database Table", "description": "CatalogManagement.dbo.Product_Description;  where Description_Type = 8" }, { "name": "General Comments", "description": "Check org/config specific mapping in CatalogProcessing.dbo.ONIXTextMapping tables;Ancillary Feed: Product_Description" },] },
        { "name": "Table of Contents", "selector": "div.[id^='desc_toc']", "data": [{ "name": "ONIX 2.1", "description": "/product/othertext;  Default Mapping: CodeList 33 TypeCode 04 (Table of Contents)" }, { "name": "ONIX 3.0", "description": "/product/collateraldedail/textcontent" }, { "name": "Export File and Stored Procedure", "description": "TextContent.txt Import_ONIX_TextContent" }, { "name": "Database Table", "description": "CatalogManagement.dbo.Product_Description;  where Description_Type = 14" }, { "name": "General Comments", "description": "Check org/config specific mapping in CatalogProcessing.dbo.ONIXTextMapping tables;Ancillary Feed: Product_Description" },] },
        { "name": "Related Titles", "selector": "div.titleRelatedDiv", "data": [{ "name": "ONIX 2.1", "description": "/product/relatedproduct" }, { "name": "ONIX 3.0", "description": "/product/relatedmaterial/relatedproduct/" }, { "name": "Export File and Stored Procedure", "description": "RelatedProduct.txt Import_ONIX_RelatedProduct" }, { "name": "Database Table", "description": "CatalogManagement.dbo.Product_RelatedProduct" }, { "name": "General Comments", "description": "Related Products have to be manually configured for each ConfigID in ;the CatalogProcessing.dbo.ONIXRelatedProductConfig table.;Ancillary Feed: Related_Products" },] },
        { "name": "Sales Rights Modal", "selector": "#popModal_inner:has(div>#salesRightHeaderText)", "data": [{ "name": "ONIX 2.1", "description": "/product/salesrights" }, { "name": "ONIX 3.0", "description": "/product/publishingdetail/salesrights" }, { "name": "Export File and Stored Procedure", "description": "Rights.txt Import_ONIX_Rights" }, { "name": "Database Table", "description": "CatalogManagement.dbo.Product_Rights" },] },
    ]

    var openToolTip = function (ele) {
        var winPrint = window.open('', '', 'left=0,top=0,width=800,height=600,toolbar=0,scrollbars=0,status=0');
        winPrint.document.write(ele.getAttribute('title').replaceAll('\n', '<br />'));
        winPrint.document.close();
    }

    var formatToolTip = function (tooltip) {
        br = '\n';
        tooltip_text = tooltip.name + br + br;
        tooltip.data.forEach(function (desc) {
            tooltip_text += desc.name + br
            tooltip_text += desc.description.replaceAll(';', '\n') + br + br
        });
        return tooltip_text
    }

    var applyToolTip = function (tooltip) {
        $(tooltip.selector).attr('title', formatToolTip(tooltip)).attr('onclick', 'ePlus.modules.support.onixToolTips.openToolTip(this)');
    };

    var initialize = function () {
        tooltip_data.forEach(applyToolTip);
        $('span.textLarge').css('color', 'red');
    };

    return {
        initialize: initialize,
        openToolTip: openToolTip
    };
})();;
; window.ePlus.modules.supportGraphs = (function () {
    var PRODUCT = 'product';
    var CLIENT = 'client';
    var GREENLINE = '#598A18';

    var getTrendChartData = function (resultType, controlId, color, title, width) {
        var url = 'api/supportTools/' + resultType + '/trendChart';
        $.ajax({
            type: "POST",
            url: url,
            cache: false,
            contentType: "application/json"
        })
            .done(function (data) {
                drawTrendChart(controlId, data, color, title, width);
            });
    }

    var openRevenueGrid = function (widgetId) {
        var url = "/GetTreelineControl.aspx?controlName=/uc/dashboard_v2/widgetItems/modules/supportRevenueGrid.ascx&widgetId=" + widgetId;
        openMultiModal({
            id: 'revenue-grid',
            url: url,
            width: "750px",
            height: "600px"
        });
    }

    var saveRevenueWidgetPrefs = function (widgetId, orgId, userId, prefType, prefName) {
        var prefString = JSON.stringify(getRevenueGraphChart(widgetId));
        var url = 'api/users/' + orgId + '/' + userId + '/preferences/' + prefType + '/' + prefName;

        $.ajax({
            type: "POST",
            url: url,
            cache: false,
            contentType: "application/json; charset=utf-8",
            data: JSON.stringify(prefString)
        });
    }

    var runRevenueChart = function (widgetId) {
        getGraphHeader(PRODUCT, widgetId);
        getGraphHeader(CLIENT, widgetId);
        $('#revenue-chart-' + widgetId).removeClass('hidden');
        $('#revenue-rerun-' + widgetId).addClass('hidden');
        $('#revenue-filter-section-' + widgetId).addClass('hidden');
        getRevenueChartData(widgetId, GREENLINE, width);
    }

    var getGraphHeader = function (filterType, widgetId) {
        var runningText = '';
        var checkCount = 0;
        var MAX_CHECKED_CHECKBOXES = 4;
        $('.' + filterType + '-checkbox-' + widgetId).each(function () {
            var $this = $(this);
            if ($this.is(":checked")) {
                if (runningText != '') {
                    runningText += ', ';
                }
                runningText += $('#' + filterType + '-check-' + $this.data('groupid')).html();
                checkCount += 1;
            }
        });
        if (checkCount === MAX_CHECKED_CHECKBOXES) {
            if (filterType === PRODUCT) {
                runningText = 'All Products';
            } else {
                runningText = 'All Customers';
            }
        } 
        $('#' + filterType + '-summary-' + widgetId).html(runningText);
    }

    var getRevenueGraphChart = function (widgetId) {
        var revenueGroups = [];
        var clientTypes = [];
        $('.product-checkbox-' + widgetId, $('#revenue-filter-section-' + widgetId)).each(function () {
            var $this = $(this);
            if ($this.is(":checked")) {
                revenueGroups.push($this.data('groupid'));
            }
        });
        $('.client-checkbox-' + widgetId, $('#revenue-filter-section-' + widgetId)).each(function () {
            var $this = $(this);
            if ($this.is(":checked")) {
                clientTypes.push($this.data('groupid'));
            }
        });

        var revenueGraph = {
            startDate: $('#rev-starting-month-' + widgetId).val(),
            endDate: $('#rev-ending-month-' + widgetId).val(),
            revenueGroups: revenueGroups,
            clientTypes: clientTypes
        }
        return revenueGraph
    }

    var getRevenueChartData = function (widgetId, color, width) {
        $('#revenue-chart-' + widgetId).html("Retrieving Data...");
        var url = 'api/supportTools/revenue';
        var revenueGraph = getRevenueGraphChart(widgetId);

        $.ajax({
            type: "POST",
            url: url,
            cache: false,
            contentType: "application/json; charset=utf-8",
            data: JSON.stringify(revenueGraph)
        })
            .done(function (data) {
                $('#revenue-chart-' + widgetId).html("Rendering Chart...");
                drawRevenueTrendChart(widgetId, data, color, width);
            });
    }

    var drawRevenueTrendChart = function (widgetId, data, color, width) {
        var minValue = (data.minValue === 0) ? 0 : data.minValue;
        var options = {
            vAxis: {
                format: '$###,##0',
                textStyle: { fontName: 'Open Sans', color: '#545454', fontSize: 10 },
                viewWindowMode: 'explicit',
                viewWindow: {
                    max: data.maxValue,
                    min: minValue
                }
            },
            textStyle: { fontName: 'Open Sans', color: '#545454', fontSize: 10 },
            lineWidth: 6,
            colors: [color],
            interpolateNulls: true,
            backgroundColor: 'transparent',
            'width': width - 10,
            'height': 140,
            'chartArea': { left: 60, top: 10, 'width': '95%', 'height': '86%' }
        };


        var data = new google.visualization.DataTable(data.chartData);
        var chart = new google.visualization.LineChart(document.getElementById('revenue-chart-' + widgetId));
        chart.draw(data, options);
    }

    var getRevenueGridData = function (widgetId) {
        var url = 'api/supportTools/revenuegrid';
        var revenueGraph = getRevenueGraphChart(widgetId);

        $.ajax({
            type: "POST",
            url: url,
            cache: false,
            contentType: "application/json; charset=utf-8",
            data: JSON.stringify(revenueGraph)
        })
            .done(function (data) {

                var i = 0;
                var options = { year: 'numeric', month: 'short' };
                data.forEach(function (value) {
                    if (i === 0) {
                        for (var key in value.monthlyTransactions) {
                            $('#header-row').append('<th>' + new Date(key).toLocaleDateString('en-US', options) + '</th>');
                        }
                    }
                    var dHtml = buildRevenueTransactionRowHtml(value);
                    $('#revenue-details-table').append(dHtml);
                    i += 1;
                });
                $('#revenue-details-loading').addClass('hidden');
                $('#revenue-details-table')
                    .removeClass('hidden')
                    .DataTable({
                        fixedHeader: true,
                        pageLength: 11
                    });
                $('#revenue-details-table_wrapper').width($('#revenue-details-table').width());

                $('#revenue-details-table').after('<div id="revenue-details-table_export" class="columnSpacedMid icon-cloud-download iconSVG" style="height: 18px; padding-top: 1px;" title="' + getRes("export_to_file") + '"></div>');

                $('#revenue-details-table_export').click(function () { exportGraphToCVS(data) });
            });
    }

    var buildRevenueTransactionRowHtml = function (value) {
        var dHtml = '<tr>';
        dHtml += '<td><div id="trans_' + value.orgId + '" title="ID: ' + value.orgId + '" class="revgrid-org-name-col dotDot">' + value.orgName + '</div></td>';
        dHtml += '<td><div class="revgrid-produce-name-col dotDot">' + value.productName + '</div></td>';
        for (var key in value.monthlyTransactions) {
            dHtml += '<td>$' + value.monthlyTransactions[key].toLocaleString('en-US', 'currency') + '</td>';
        }
        dHtml += '</tr>';
        return dHtml;
    }

    var drawTrendChart = function (controlId, jsonData, color, title, width) {
        var options = {
            hAxis: {
                direction: '-1',
            },
            vAxis: {
                title: title,
                format: '#.#',
                titleTextStyle: { fontName: 'Open Sans', color: '#545454', fontSize: 12, bold: true },
                textStyle: { fontName: 'Open Sans', color: '#545454', fontSize: 12 }
            },
            textStyle: { fontName: 'Open Sans', color: '#545454', fontSize: 12 },
            lineWidth: 4,
            colors: [color],
            interpolateNulls: true,
            backgroundColor: 'transparent',
            'width': width - 10,
            'height': 80,
            'chartArea': { left: 60, top: 10, 'width': '88%', 'height': '80%' }
        };


        var data = new google.visualization.DataTable(jsonData);
        var chart = new google.visualization.LineChart(document.getElementById(controlId));
        chart.draw(data, options);
    }

    var renderFreshnessPie = function (clientType, laneId) {
        var url = 'api/organizations/uploadFreshness/' + clientType;
        $.ajax({
            type: 'GET',
            url: url,
            cache: false,
            contentType: 'application/json'
        })
            .done(function (data) {
                var jsonData = data.dataString;
                $('#freshCount_' + clientType).html(data.count);
                var colors = ['#000000', '#92D050', '#CCFF66', '#FFEA00', '#fc601f', '#598A18'];

                var defaultColors = ['#000000', '#92D050', '#CCFF66', '#FFEA00', '#FC601F', '#598A18'];
                var options = {
                    backgroundColor: 'transparent',
                    chartArea: { left: '10%', top: '0%', width: '80%', height: '80%' },
                    colors: (colors === undefined) ? defaultColors : colors,
                    height: 110,
                    is3D: false,
                    fontSize: 8,
                    tooltip: { trigger: 'none' },
                    legend: { position: 'none' },
                    sliceVisibilityThreshold: 1 / 100000,
                    slices: {
                        0: { offset: 0.0 },
                        1: { offset: 0.0 },
                        2: { offset: 0.0 },
                        3: { offset: 0.0 },
                        4: { offset: 0.0 }
                    }
                };
                var chartData = new google.visualization.DataTable(jsonData);

                var chart = new google.visualization.PieChart(document.getElementById('chartFrame_' + laneId + '_' + clientType));
                function selectHandler() {
                    var selectedItem = chart.getSelection()[0];
                    if (selectedItem) {
                        populateWedgeDetail(clientType, selectedItem.row, laneId);
                    }
                }

                google.visualization.events.addListener(chart, 'select', selectHandler);

                chart.draw(chartData, options);
            });
    }

    var populateWedgeDetail = function (clientType, wedge, laneId) {
        createModalDialog("600px", "500px", '<div id="wedgeContent_' + laneId + '" class="progressBackground" style="height: 420px; overflow-x: hidden; overflow-y: auto;padding: 40px;"></div>', null, null, null, null, '900005');
        var url = 'api/organizations/uploadFreshness/' + clientType + '/' + wedge;
        $.ajax({
            type: 'GET',
            url: url,
            cache: false,
            contentType: 'application/json'
        })
            .done(function (data) {
                var cssClassNames = {
                    tableRow: 'freshTableStyle',
                    oddTableRow: 'freshTableStyle'
                }
                var tableData = new google.visualization.DataTable(data);
                var table = new google.visualization.Table(document.getElementById('wedgeContent_' + laneId));
                tableData.setProperty(0, 0, 'style', 'width:300px');
                table.draw(tableData, { showRowNumber: true, height: '100%', width: '100%', 'cssClassNames': cssClassNames, allowHtml: true });
                $('#wedgeContent_' + laneId).removeClass('progressBackground');
                $('#popModal_inner').html($('#wedgeContainer').html());
                $('#wedgeContent_' + laneId + ' .org-Id').on('click', function () {
                    var orgId = $(this).data('orgid');
                    openAccountSupport(orgId);
                });
            });
    }

    var drawArPie = function (laneId) {
        google.charts.load('current', { packages: ['table'] });
        var url = "api/organizations/receivables";

        $.ajax({
            type: 'GET',
            url: url,
            cache: false,
            contentType: 'application/json'
        })
            .done(function (data) {
                $('#overdueTotal' + laneId).html(data.overdue.toLocaleString('US', { style: 'currency', currency: 'USD' }).slice(0, -3) + ' Late A/R');
                var jsonData = data.dataString;
                var colors = ['#598A18', '#92D050', '#CCFF66', '#FFEA00', '#fc601f'];
                drawReceivablesPie(jsonData, colors, laneId);

                var cssClassNames = {
                    tableRow: 'overallStyles',
                    oddTableRow: 'overallStyles',
                    headerRow: 'hidden'
                }
                var data = new google.visualization.DataTable(jsonData);
                var table = new google.visualization.Table(document.getElementById('receivablesTable' + laneId));

                function selectHandler() {
                    var selectedItem = table.getSelection()[0];
                    if (selectedItem) {
                        openArSummary(selectedItem.row)
                    }
                }
                google.visualization.events.addListener(table, 'select', selectHandler);
                table.draw(data, { showRowNumber: false, width: '100%', height: '100%', 'cssClassNames': cssClassNames });
            });
    }

    var drawReceivablesPie = function (jsonData, colors, laneId) {
        var defaultColors = ['#92D050', '#CCFF66', '#FFEA00', '#FC601F', '#598A18'];
        var options = {
            backgroundColor: 'transparent',
            chartArea: { left: '15%', top: '15%', width: '70%', height: '70%' },
            colors: colors ? colors : defaultColors,
            height: 160,
            width: 160,
            is3D: false,
            fontSize: 10,
            tooltip: { ignoreBounds: true },
            legend: { position: 'none' },
            sliceVisibilityThreshold: 1 / 100000,
            slices: {
                0: { offset: 0.0 },
                1: { offset: 0.0 },
                2: { offset: 0.0 },
                3: { offset: 0.0 },
                4: { offset: 0.0 }, 5: { offset: 0.0 }
            }
        };
        var data = new google.visualization.DataTable(jsonData);
        var chart_div = document.getElementById('chart-frame' + laneId);
        var chart = new google.visualization.PieChart(chart_div);

        function selectHandler() {
            var selectedItem = chart.getSelection()[0];
            if (selectedItem) {
                openArSummary(selectedItem.row)
            }
        }
        google.visualization.events.addListener(chart, 'select', selectHandler);
        chart.draw(data, options);
    }


    var openArSummary = function (status) {
        var url = '/GetTreelineControl.aspx?controlName=/uc/organization/accountsReceivable.ascx&status=' + (status + 1);
        openModal(url, '90%', '90%');
    }

    var drawBillingGraph = function (revPeriod, clientId) {
        $('#line-bill-' + revPeriod).html(getRes('loading'));
        var url = 'api/organizations/revenue/' + clientId + '/' + revPeriod;
        $.ajax({
            type: "GET",
            url: url,
            cache: false,
            contentType: "application/json"
        })
            .done(function (data) {
                var jsonData = data;
                var options = {
                    hAxis: {
                        titleTextStyle: { fontName: 'Open Sans', color: '#545454', fontSize: 9 },
                        textStyle: { fontName: 'Open Sans', color: '#545454', fontSize: 9 }
                    },
                    textStyle: { fontName: 'Open Sans', color: '#545454', fontSize: 12 },
                    lineWidth: 4,
                    colors: ['#2767b8'],
                    interpolateNulls: true,
                    backgroundColor: '#ffffff',
                    width: 430,
                    height: 90,
                    'chartArea': { left: 0, top: 10, 'width': '100%', 'height': '70%' }
                };

                var data = new google.visualization.DataTable(jsonData);
                var chart = new google.visualization.LineChart(document.getElementById('line-bill-' + revPeriod));
                chart.draw(data, options);
            });
    }

    var loadAccountProductBreakdown = function (clientId) {
        $("#revenue-breakdown").html("Loading...");
        $.url = "/GetTreelineControl.aspx?controlName=/uc/organization/accountProductBreakdown.ascx&clientId=" + clientId;
        $("#revenue-breakdown").load($.url, function () {
            $("#revenue-breakdown").removeClass("progressBackground");
        });
    }

    var getRevenueBreakdownData = function (clientId) {
        var url = "api/organizations/products?clientId=" + clientId;
        $.ajax({
            type: "GET",
            url: url,
            cache: false,
            contentType: "application/json"
        })
            .done(function (data) {
                var title = data.revenueTotal.toLocaleString('US', { style: 'currency', currency: 'USD' }).slice(0, -3) + " 3yr Revenue";
                var jsonData = data.googleTable;
                var colors = ['#598A18', '#92D050', '#CCFF66', '#FFEA00', '#fc601f'];
                drawRevenueBreakdownPie(jsonData, colors, title);

                var tableMap = JSON.parse(data.tableData);
                window.tableMap = tableMap;
                $.each(tableMap, function (key, value) {
                    var dHtml = getRevenueBreakdownRowHtml(value);
                    $("#revenue-break-table").append(dHtml);

                    $("#transType_" + value.TransactionType).webuiPopover({
                        type: 'async',
                        cache: false,
                        backdrop: true,
                        placement: 'bottom',
                        multi: true,
                        dismissible: false,
                        closeable: true,
                        width: 400,
                        url: '/GetTreelineControl.aspx?controlName=/uc/organization/accountTransactions.ascx&clientId=' + clientId + '&transactionType=' + value.TransactionType,
                    });
                });
            });
    }

    var getRevenueBreakdownRowHtml = function (value) {
        var dHtml = "<tr>";
        dHtml += "<td><div id='transType_" + value.TransactionType + "'>" + value.TransactionTypeName + "</div></td>";
        dHtml += "<td>" + value.Amount.toLocaleString('US', { style: 'currency', currency: 'USD' }).slice(0, -3) + "</td>";
        dHtml += "<td>" + value.Unpaid.toLocaleString('US', { style: 'currency', currency: 'USD' }).slice(0, -3) + "</td>";
        dHtml += "</tr>";
        return dHtml;
    }

    var drawRevenueBreakdownPie = function (jsonData, colors, title) {
        var defaultColors = ['#92D050', '#CCFF66', '#FFEA00', '#FC601F', '#598A18'];
        var options = {
            backgroundColor: 'transparent',
            chartArea: { left: '15%', top: '15%', width: '70%', height: '70%' },
            colors: (colors === undefined) ? defaultColors : colors,
            height: 160,
            width: 160,
            is3D: false,
            fontSize: 10,
            title: title,
            tooltip: { ignoreBounds: true },
            legend: { position: 'none' },
            sliceVisibilityThreshold: 1 / 100000,
            slices: {
                0: { offset: 0.0 },
                1: { offset: 0.0 },
                2: { offset: 0.0 },
                3: { offset: 0.0 },
                4: { offset: 0.0 }, 5: { offset: 0.0 }
            }
        };
        var data = new google.visualization.DataTable(jsonData);
        var chart_div = document.getElementById('revenue-break');
        var chart = new google.visualization.PieChart(chart_div);

        function selectHandler() {
            var selectedItem = chart.getSelection()[0];
            if (selectedItem) {
                openARSsummary(selectedItem.row)
            }
        }
        google.visualization.events.addListener(chart, 'select', selectHandler);
        chart.draw(data, options);
    }

    var exportGraphToCVS = function (data) {
        const options = { year: 'numeric', month: 'short' };
        var header = {
            'orgId': 'Org Id',
            'orgName': 'Org Name',
            'productName': 'Product Name'
        }

        for (var key in data[0].monthlyTransactions) {
            header['monthlyTransactions.' + key] = new Date(key).toLocaleDateString('en-US', options);
        }

        for (var i = 0; i < data.length; i++) {
            data[i] = ePlus.modules.export.flatten(data[i]);
        }

        ePlus.modules.export.downloadCSVFromJson('Revenue.csv', Object.values(header), Object.keys(header), data, '', 'orgName');
    }

    return {
        drawTrendChart: drawTrendChart,
        saveRevenueWidgetPrefs: saveRevenueWidgetPrefs,
        openRevenueGrid: openRevenueGrid,
        runRevenueChart: runRevenueChart,
        getRevenueGraphChart: getRevenueGraphChart,
        getRevenueChartData: getRevenueChartData,
        getRevenueGridData: getRevenueGridData,
        renderFreshnessPie: renderFreshnessPie,
        populateWedgeDetail: populateWedgeDetail,
        drawReceivablesPie: drawReceivablesPie,
        getTrendChartData: getTrendChartData,
        drawArPie: drawArPie,
        openArSummary: openArSummary,
        drawBillingGraph: drawBillingGraph,
        loadAccountProductBreakdown: loadAccountProductBreakdown,
        getRevenueBreakdownData: getRevenueBreakdownData,
        drawRevenueBreakdownPie: drawRevenueBreakdownPie
    }
})();;
window.ePlus.modules.mops = (function () {
    var initiativeMopWidget = function (
        widgetId,
        mopId,
        mopParameter,
        orgId,
        userId,
        prefType,
        width
    ) {
        getMops(widgetId, mopId, mopParameter);
        if (mopId > 0) {
            getMopTrend(widgetId, width);
        }
        $("#mops-" + widgetId).on("change", function () {
            saveMopsWidgetPrefs(orgId, userId, prefType, "mop-id-" + widgetId, 0);

            saveMopsWidgetPrefs(
                orgId,
                userId,
                prefType,
                "mop-param-" + widgetId,
                null
            );

            $("#mop-trend-list-" + widgetId).html("");
            var selection = $("option:selected", this);
            $("#mop-parameters-" + widgetId).addClass("hidden");
            $("#mop-parameters-" + widgetId + " option").remove();

            if (selection.data("params") > 0) {
                getMopParameters(widgetId, $(this).val());
            } else {
                if ($(this).val() > 0) {
                    $("#mop-run-" + widgetId).removeClass("hidden");
                    $("#mop-trend-list-" + widgetId).addClass("hidden");
                    saveMopsWidgetPrefs(
                        orgId,
                        userId,
                        prefType,
                        "mop-id-" + widgetId,
                        $(this).val()
                    );
                } else {
                    $("#mop-run-" + widgetId).addClass("hidden");
                    $("#mop-trend-list-" + widgetId).removeClass("hidden");
                }
            }

            saveMopsWidgetPrefs(
                orgId,
                userId,
                prefType,
                "mop-procedure-" + widgetId,
                selection.data("procedure")
            );
        });
        $("#mop-parameters-" + widgetId).on("change", function () {
            $("#mop-trend-list-" + widgetId).html("");
            var selection = $("option:selected", this);

            var parameter = {
                param: selection.data("param"),
                value: $(this).val()
            };

            saveMopsWidgetPrefs(
                orgId,
                userId,
                prefType,
                "mop-param-" + widgetId,
                parameter
            );

            saveMopsWidgetPrefs(
                orgId,
                userId,
                prefType,
                "mop-id-" + widgetId,
                $("#mops-" + widgetId).val()
            );

            if ($(this).val() > 0) {
                $("#mop-run-" + widgetId).removeClass("hidden");
                $("#mop-trend-list-" + widgetId).addClass("hidden");
            } else {
                $("#mop-run-" + widgetId).addClass("hidden");
                $("#mop-trend-list-" + widgetId).removeClass("hidden");
            }
        });
        $("#mop-run-" + widgetId).on("click", function () {
            $("#mop-run-" + widgetId).addClass("hidden");
            $("#mop-trend-list-" + widgetId).removeClass("hidden");
            getMopTrend(widgetId, width);
        });
        $("#mop-grid-link-" + widgetId).on("click", function () {
            openMopsGrid(widgetId);
        });
    };

    var getMops = function (widgetId, mopId, mopParameter) {
        var url = "api/mops/mops";
        var dHtml = "";
        $.ajax({
            type: "GET",
            url: url,
            cache: false,
            contentType: "application/json"
        }).done(function (mops) {
            dHtml += createMopDefault();
            mops.map(function (mop) {
                dHtml += createMopOption(mop, mopId);
            });
            $("#mops-" + widgetId).append(dHtml);
            if (mopId > 0) {
                $("#mops-" + widgetId).val(mopId);
                if (mopParameter) {
                    getMopParameters(widgetId, mopId, mopParameter);
                }
            }
        });
    };

    var getMopParameters = function (widgetId, mopId, mopParameter) {
        var url = "api/mops/" + mopId + "/mopParameters";
        var dHtml = "";
        $.ajax({
            type: "GET",
            url: url,
            cache: false,
            contentType: "application/json"
        }).done(function (mopParameters) {
            dHtml += createMopParameterDefault();
            mopParameters.map(function (mopParameter) {
                dHtml += createMopParameterOption(mopParameter);
            });
            $("#mop-parameters-" + widgetId).append(dHtml);
            $("#mop-parameters-" + widgetId).removeClass("hidden");
            if (mopParameter) {
                $("#mop-parameters-" + widgetId).val(mopParameter);
            }
        });
    };

    var createMopDefault = function () {
        return '<option value="0">{Select a MOP!}</option>';
    };

    var createMopOption = function (mop) {
        return (
            '<option data-procedure="' +
            mop.procedure +
            '" value="' +
            mop.id +
            '" data-title="' +
            mop.title +
            '" data-params="' +
            mop.params +
            '">' +
            mop.name +
            "</option>"
        );
    };

    var createMopParameterDefault = function () {
        return '<option value="0">{Select a Parameter!}</option>';
    };

    var createMopParameterOption = function (mp) {
        return (
            '<option value="' +
            mp.value +
            '" data-param="' +
            mp.param +
            '">' +
            mp.name +
            "</option>"
        );
    };

    var getMopTrend = function (widgetId, width) {
        var url = "api/mops/" + widgetId + "/mopTrend";
        $.ajax({
            type: "GET",
            url: url,
            cache: false,
            contentType: "application/json"
        }).done(function (data) {
            drawTrendChart("mop-trend-list-" + widgetId, data, "#ffc321", width);
        });
    };

    var getMopDetails = function (widgetId) {
        var exportHeader = [];
        var url = "api/mops/" + widgetId + "/mopDetails";
        $.ajax({
            type: "GET",
            url: url,
            cache: false,
            contentType: "application/json"
        }).done(function (data) {
            var i = 0;
            var results = data.results;
            var mopColumns = data.mopColumns;
            var dHtml = "<thead><tr>";
            mopColumns.forEach(function (mopColumn) {
                if (mopColumn.doDisplay) {
                    dHtml += "<th>";
                    dHtml += mopColumn.header;
                    dHtml += "</th>";
                }
                exportHeader[mopColumn.attribute] = mopColumn.attribute;
            });
            dHtml += "</tr></thead><tbody>";
            results.forEach(function (row) {
                dHtml += "<tr>";
                mopColumns.forEach(function (mopColumn) {
                    dHtml += renderCell(mopColumn, row, widgetId);
                });
                dHtml += "</tr>";
                i += 1;
            });
            dHtml += "</tbody>";
            $('#mop-details-table-' + widgetId).append(dHtml);

            $('#mop-details-loading-' + widgetId).addClass('hidden');

            var columns = mopColumns.filter(function (mp) {
                return mp.doDisplay;
            }).length;

            $('#mop-details-table-' + widgetId)
                .removeClass('hidden')
                .DataTable({
                    pageLength: 12,
                    fixedHeader: true,
                    order: [[columns - 1, "desc"]],
                    lengthChange: false
                });
            $('#mop-details-table-' + widgetId + '_wrapper').width($('#mop-details-table-' + widgetId).width());

            $('#mop-details-table-' + widgetId).on('click', '.mop-' + widgetId + '-account-link', function () {
                var orgId = $(this).data("orgid");
                openAccountSupport(orgId);
            });

            $('#mop-details-table-' + widgetId).on('click', '.mop-' + widgetId + '-user-link', function () {
                var appUserId = $(this).data("appuserid");
                openUserSupport(appUserId);
            });
        });
    };

    var renderCell = function (mopColumn, row, widgetId) {
        if (mopColumn && mopColumn.doDisplay) {
            var dHtml = "<td>";
            if (mopColumn.attribute == "orgName") {
                dHtml +=
                    '<div class="accFont bold clickable mop-' +
                    widgetId +
                    '-account-link" data-orgid="' +
                    row.orgId +
                    '">' +
                    row[mopColumn.attribute] +
                    "</div>";
            } else if (mopColumn.attribute == "userName") {
                dHtml +=
                    '<div class="accFont bold clickable mop-' +
                    widgetId +
                    '-user-link" data-appuserid="' +
                    row.appUserId +
                    '">' +
                    row[mopColumn.attribute] +
                    "</div>";
            } else if (mopColumn.dataType == "date") {
                var d = new Date(row[mopColumn.attribute]);
                dHtml +=
                    d.getFullYear() + "/" + (d.getMonth() * 1 + 1) + "/" + d.getDate();
            } else {
                if (row[mopColumn.attribute]) {
                    dHtml += row[mopColumn.attribute];
                }
            }
            dHtml += "</td>";
        }
        return dHtml;
    };

    var drawTrendChart = function (controlId, jsonData, color, width) {
        var options = {
            hAxis: {
                direction: "-1"
            },
            textStyle: { fontName: "Open Sans", color: "#545454", fontSize: 12 },
            lineWidth: 4,
            colors: [color],
            interpolateNulls: true,
            backgroundColor: "transparent",
            width: width - 10,
            height: 120,
            chartArea: { left: 20, top: 10, width: "96%", height: "67%" }
        };

        var data = new google.visualization.DataTable(jsonData);
        var chart = new google.visualization.LineChart(
            document.getElementById(controlId)
        );
        chart.draw(data, options);
    };

    var saveMopsWidgetPrefs = function (
        orgId,
        userId,
        prefType,
        prefName,
        prefValue
    ) {
        prefValue = JSON.stringify(prefValue);
        var url =
            "api/users/" +
            orgId +
            "/" +
            userId +
            "/preferences/" +
            prefType +
            "/" +
            prefName;

        $.ajax({
            type: "POST",
            url: url,
            cache: false,
            contentType: "application/json; charset=utf-8",
            data: JSON.stringify(prefValue)
        });
    };

    var openMopsGrid = function (widgetId) {
        var url =
            "/GetTreelineControl.aspx?controlName=/uc/dashboard_v2/widgetItems/modules/supportMopGrid.ascx&widgetId=" +
            widgetId;
        openMultiModal({
            id: "mop-grid",
            url: url,
            width: "750px",
            height: "600px"
        });
    };

    return {
        initiativeMopWidget: initiativeMopWidget,
        getMopDetails: getMopDetails
    };
})();
;
; window.ePlus.modules.clientManagement = (function () {
    var getLibraryAdvocacy = function () {
        var url = 'api/supportTools/orgs/advocacies/summaries';
        $.ajax({
            type: 'GET',
            url: url,
            cache: false,
            contentType: 'application/json'
        })
            .done(function (data) {
                for (var org in data) {
                    for (var level in data[org]) {
                        if ($('#a-' + org + '-' + level).length > 0) {
                            $('#a-' + org + '-' + level)
                                .html(data[org][level])
                                .removeClass('hidden');
                        }
                    }
                }
                $('#library-management-table').DataTable({
                    paging: false,
                    fixedHeader: true
                });
            });
    }

    var getPublisherCount = function () {
        if ($('#pub-inactive-check').hasClass('box-checked')) {
            $('#number-publishers').html($('.publisher-client').length);
        } else {
            $('#number-publishers').html($('.publisher-client').not('.hidden').length);
        }
    }

    var getProfileCount = function () {
        if ($('#profile-inactive-check').hasClass('box-checked')) {
            $('#number-profile').html($('.drc-profile').length);
        } else {
            $('#number-profile').html($('.drc-profile').not('.hidden').length);
        }
    }

    var getRevenueTransactions = function (clientId, transactionType) {
        var url = 'api/organizations/transactions/' + clientId + '/' + transactionType;
        $.ajax({
            type: 'GET',
            url: url,
            cache: false,
            contentType: 'application/json'
        })
            .done(function (data) {
                window.tableMap = data;
                data.forEach(function (value) {
                    var dHtml = buildRevenueTransactionRowHtml(value);
                    $('#revenue-transaction-table-' + transactionType).append(dHtml);
                    $('#trans_' + value.transactionId).webuiPopover({
                        type: 'async',
                        cache: false,
                        backdrop: true,
                        multi: true,
                        dismissible: false,
                        closeable: true,
                        width: 400,
                        url: '/GetTreelineControl.aspx?controlName=/uc/support/transactionDetail.ascx&transactionId=' + value.transactionId,
                    });
                });
            });
    }

    var buildRevenueTransactionRowHtml = function (value) {
        var dHtml = '<tr>';
        dHtml += '<td><div id="trans_' + value.transactionId + '" title="ID: ' + value.transactionId + '">' + value.start.slice(0, -12) + '</div></td>';
        if (value.displayName) {
            dHtml += '<td>' + value.displayName + '</td>';
        } else {
            dHtml += '<td></td>';
        }
        dHtml += '<td>' + value.duration + '</td>';
        dHtml += '<td>' + value.amount.toLocaleString('US', { style: 'currency', currency: 'USD' }).slice(0, -3) + '</td>';
        if (value.invoices) {
            dHtml += '<td>' + value.invoices + '</td>';
            var paid = '-';
            if (value.paid) {
                paid = value.paid.toLocaleString('US', { style: 'currency', currency: 'USD' }).slice(0, -3);
            }
            dHtml += '<td>' + paid + '</td>';
            var deposits = '-';
            if (value.deposits) {
                deposits = value.deposits;
            }
            dHtml += '<td>' + deposits + '</td>';
        } else {
            dHtml += '<td>-</td>';
            dHtml += '<td>-</td>';
            dHtml += '<td>-</td>';
        }
        dHtml += '</tr>';
        return dHtml;
    }

    var getLibraryDashAdvocacy = function () {
        var url = 'api/supportTools/advocacies/levels/summaries';

        $('.adv-0').addClass('bg-orange');
        $('.adv-1').addClass('bg-e9ebec');
        $('.adv-2').addClass('bg-red');
        $('.adv-3').addClass('bg-yellow');
        $('.adv-4').addClass('bg-light-green');
        $('.adv-5').addClass('bg-dark-green');

        $.ajax({
            type: 'GET',
            url: url,
            cache: false,
            contentType: 'application/json'
        })
            .done(function (data) {
                for (var service in data) {
                    for (var level in data[service]) {
                        $('#adv-' + service + '-' + level + '-num').html(data[service][level]);
                        $('#adv-' + service + '-' + level + '-num').removeClass('hidden');
                    }
                }
            });
    }

    var getLibraryDashUsage = function () {
        var url = 'api/supportTools/libraries/usages';

        $.ajax({
            type: 'GET',
            url: url,
            cache: false,
            contentType: 'application/json'
        })
            .done(function (data) {
                var arrayLength = data.length;
                window.tempObject = data
                for (var i = 0; i < arrayLength; i++) {
                    $('#ld-' + data[i].serviceId + '-0-num').html(data[i].usageByPeriod[getEnumValue('usageTimePeriod', 'INLASTWEEK')]);
                    $('#ld-' + data[i].serviceId + '-0-perc').html(formatUsagePerc(data[i].usageByPeriod[getEnumValue('usageTimePeriod', 'INLASTWEEK')], data[i].onboarded, data[i].serviceId + '-0'));
                    $('#ld-' + data[i].serviceId + '-1-num').html(data[i].usageByPeriod[getEnumValue('usageTimePeriod', 'INLASTMONTH')]);
                    $('#ld-' + data[i].serviceId + '-1-perc').html(formatUsagePerc(data[i].usageByPeriod[getEnumValue('usageTimePeriod', 'INLASTMONTH')], data[i].onboarded, data[i].serviceId + '-1'));
                    $('#ld-' + data[i].serviceId + '-2-num').html(data[i].usageByPeriod[getEnumValue('usageTimePeriod', 'INLASTTHREEMONTHS')]);
                    $('#ld-' + data[i].serviceId + '-2-perc').html(formatUsagePerc(data[i].usageByPeriod[getEnumValue('usageTimePeriod', 'INLASTTHREEMONTHS')], data[i].onboarded, data[i].serviceId + '-2'));
                    $('#ld-' + data[i].serviceId + '-3-num').html(data[i].usageByPeriod[getEnumValue('usageTimePeriod', 'INLASTSIXMONTHS')]);
                    $('#ld-' + data[i].serviceId + '-3-perc').html(formatUsagePerc(data[i].usageByPeriod[getEnumValue('usageTimePeriod', 'INLASTSIXMONTHS')], data[i].onboarded, data[i].serviceId + '-3'));
                }
            });
    }

    var formatUsagePerc = function (numerator, denominator, dependentClass) {
        var perc = parseInt((numerator / denominator) * 100);
        $('.' + dependentClass).removeClass('bg-light-gray');
        if (perc == 100) {
            $('.' + dependentClass).addClass('bg-dark-green');
            perc = 'All';
        } else if (perc > 75) {
            $('.' + dependentClass).addClass('bg-light-green');
            perc = perc + '%';
        } else if (perc > 50) {
            $('.' + dependentClass).addClass('bg-yellow');
            perc = perc + '%';
        } else if (perc > 25) {
            $('.' + dependentClass).addClass('bg-orange');
            perc = perc + '%';
        } else if (perc >= 0) {
            $('.' + dependentClass).addClass('bg-red');
            perc = perc + '%';
        }
        return perc
    }

    var loadAdvocacySummaryIcon = function (appUserId) {
        $.url = '/GetTreelineControl.aspx?controlName=/uc/support/advocacy/userAdvocacyBadge.ascx&appUserId=' + appUserId;
        $('#advocacy-summary-icon').load($.url);
    }

    var saveUserAdvocacy = function (advocacyId, advocacyLevel, appUserId, adminAppUserId) {
        var productId = $('#product-' + advocacyId).val();
        if ((advocacyLevel > 0 && productId > 0) || advocacyLevel == -1) {
            var advocacyInfo = {
                adminAppUserId: adminAppUserId,
                advocacyId: advocacyId,
                advocacyLevel: advocacyLevel,
                appUserId: appUserId,
                engagementLevel: $('#engagement-' + advocacyId).val(),
                productId: productId
            };

            $.ajax({
                type: 'PUT',
                url: 'api/supportTools/user/' + appUserId + '/advocacy',
                dataType: 'json',
                contentType: 'application/json; charset=utf-8',
                data: JSON.stringify(advocacyInfo)
            })
                .done(function (data) {
                    if ($('#library-management-table').length > 0) {
                        $('.advocate-number').addClass('hidden');

                        $('#library-management-table').DataTable().destroy();

                        getLibraryAdvocacy();
                    }
                    if ($('#account-section-' + getEnumValue('supportAccountMenu', 'ADVOCACY')).length > 0) {
                        window.ePlus.modules.support.loadAccountAdvocacy(getEnumValue('supportAccountMenu', 'ADVOCACY'), $('#account-modal-org').html());
                    }
                    if ($('#library-advocacy-summary').length > 0) {
                        getLibraryDashAdvocacy();
                    }
                    openUserAdvocacy(appUserId);
                    loadAdvocacySummaryIcon(appUserId);
                });
        } else {
            alert('You must set at least the product type and advocacy level. Engagement level is optional.')
        }
    }

    var openPublisherClientManagement = function () {
        var url = '/GetTreelineControl.aspx?controlName=/uc/dashboard_v2/widgetItems/modules/supportPublisherMgmtList.ascx';
        openMultiModal({
            id: "support-publisher-management-list",
            url: url,
            width: '90%',
            height: '90%'
        });
    }

    var openLibraryAnalyticsCustomerManagement = function () {
        var url = '/GetTreelineControl.aspx?controlName=/uc/dashboard_v2/widgetItems/modules/supportLibraryMgmtList.ascx';
        openMultiModal({
            id: "support-library-management-list",
            url: url,
            width: '90%',
            height: '90%'
        });
    }

    var openDrcManagement = function () {
        var url = '/GetTreelineControl.aspx?controlName=/uc/support/manageDrcProfiles.ascx';
        openMultiModal({
            id: "support-drc-management-list",
            url: url,
            width: '90%',
            height: '90%'
        });
    }

    var clearPublisherSearch = function () {
        $("#catalogSearchWrapper").html("");
        $("#publisher-org-search").val("");
        $("#publisher-org-search").removeClass("hasPlaceholder");

        CheckPlaceholder('publisher-org-search', getRes('publisher_name'));

        $("#publisher-org-search").focus();
    }

    return {
        getLibraryAdvocacy: getLibraryAdvocacy,
        getPublisherCount: getPublisherCount,
        getProfileCount: getProfileCount,
        getRevenueTransactions: getRevenueTransactions,
        buildRevenueTransactionRowHtml: buildRevenueTransactionRowHtml,
        getLibraryDashAdvocacy: getLibraryDashAdvocacy,
        getLibraryDashUsage: getLibraryDashUsage,
        formatUsagePerc: formatUsagePerc,
        loadAdvocacySummaryIcon: loadAdvocacySummaryIcon,
        saveUserAdvocacy: saveUserAdvocacy,
        openPublisherClientManagement: openPublisherClientManagement,
        openLibraryAnalyticsCustomerManagement: openLibraryAnalyticsCustomerManagement,
        openDrcManagement: openDrcManagement,
        clearPublisherSearch: clearPublisherSearch
    }
})();;
(function (imne, $, _, CKEDITOR, undefined) {
    // Private
    var defaults = {
        width: '100%',
        height: 200
    },
        settings,
        editorId,
        $editor,
        ckEditorInstance,
        clickSelector,
        clickEvent,
        beforeUnloadEvent;

    var res = window.ePlus.resources;
    var originalNote;
    var saveInProgress = false;

    function setIsSaving(isSaving) {
        saveInProgress = isSaving;
    }

    function isSaving() {
        return saveInProgress;
    }

    function getMarkupNote() {
        return $('#markupNote' + settings.sku);
    }

    function getMarkupNoteToggle() {
        return $('#toggleMarkupNotesDiv' + settings.sku);
    }

    function activatePageBlocks() {
        disableMarkupNoteEditor(settings.sku);
        $('#autoComplete').prop('disabled', true); // Prevent interaction with jump-to autocomplete
        enableMarkupFooterBlock();                 // Prevent interaction with the markup bar
        WebuiPopovers.disableAll();                // Prevent interaction with popovers

        var message = res.getRes('warning_markup_editor_close').replace('{0}', settings.sku);

        // In addition to blocking the markup bar, we also want to capture any
        // DOM events that should trigger closing the inline markup note editor.
        // If the user triggers any of the events below, we want to prompt
        // the user to let them know that continuing with the action will close
        // the markup editor dialog without saving changes.

        // 1.) Page interactions (i.e. opening dialog boxes or hashchange events)
        window.interactionHandler = function (e, callback, cancelCallback) {
            var activeMarkupNoteEditorSku = getListViewProperty('activeMarkupNoteEditor');
            openNavigateToOpenMarkupEditorModal(activeMarkupNoteEditorSku, function () {
                closeEditor();

                if (typeof callback === 'function') {
                    callback(e);
                }
            });
        }

        // 2.) Clicking links/buttons on the page
        $('#interiorPageContent').on(clickEvent, clickSelector, function (e) {
            var $self = $(this);

            // If this is a click inside the CKEditor, pass it through
            if ($self.parents('.cke').length) {
                return true;
            }

            if (e) {
                e.preventDefault();
                e.stopPropagation();
            }

            // Leverage the interaction handler above
            $(window).trigger('interaction', [function () {
                $self[0].click();
            }]);
        });

        // 3.) Reload/navigate away from the page
        $(window).off(beforeUnloadEvent).on(beforeUnloadEvent, function (e) {
            e = e || window.event;
            e.returnValue = message;

            return message; // Most modern browsers will ignore this due to scammers, but it doesn't hurt
        });
    }

    function deactivatePageBlocks() {
        $(window).off(beforeUnloadEvent);
        window.interactionHandler = null;
        $('#interiorPageContent').off(clickEvent, clickSelector);
        $('#autoComplete').prop('disabled', false);
        disableMarkupFooterBlock();
        WebuiPopovers.enableAll();
        setTimeout(function () {
            enableMarkupNoteEditor(settings.sku);
        }, 250);
        ePlus.modules.listView.setListViewProperty('activeMarkupNoteEditor', null);
    }

    function initializeEditor() {
        var $note = getMarkupNote();
        var $noteToggle = getMarkupNoteToggle();
        var initializedNote;

        $note.hide();
        $noteToggle.hide();
        $editor.show();

        originalNote = $editor.val();

        ckEditorInstance = CKEDITOR.replace(editorId, {
            language: ePlus.user.culture,
            disableNativeSpellChecker: false,
            width: settings.width,
            height: settings.height,
            extraPlugins: 'ajaxsave,closebutton,lineutils,widget,image2',
            toolbar: [
                { name: 'save', items: ['AjaxSave'] },
                { name: 'close', items: ['CloseButton'] },
                { name: 'font', items: ['FontSize'] },
                { name: 'styles', items: ['Bold', 'Italic', 'Underline', 'Strike', 'RemoveFormat'] },
                { name: 'format', items: ['NumberedList', 'BulletedList'] },
                { name: 'color', items: ['TextColor', 'BGColor'] },
                { name: 'links', items: ['Image', 'Link', 'Unlink'] }
            ],
            on: {
                instanceReady: function (e) {
                    // Set editor default font color to match E+ font preference
                    // This needs to be done before we setData to avoid Javascript errors
                    var userFontColor = ePlus.user.getFontColorPreference(),
                        noteFontColor = ePlus.user.getPreference('markup', 'noteFontColor'),
                        noteFontSize = ePlus.user.getPreference('markup', 'noteFontSize');

                    e.editor.document.getBody().setStyle("color", userFontColor);

                    function initialize() {
                        e.editor.focus();
                    }

                    if ((noteFontColor || noteFontSize) && !e.editor.getData()) {
                        $markupNoteSpan = $("<span>&#8203;</span>"); // &#8203; prevents CKEditor from removing empty span

                        if (noteFontColor) {
                            $markupNoteSpan.css('color', noteFontColor);
                        }

                        if (noteFontSize) {
                            $markupNoteSpan.css('font-size', noteFontSize);
                        }

                        e.editor.setData($markupNoteSpan.get(0).outerHTML, function () {
                            initialize();
                        });
                    } else {
                        initialize();
                    }

                    initializedNote = e.editor.getData();
                },
                ajaxsave: function (e) {
                    saveMarkupNote(e.editor);
                },
                closebutton: function (e) {
                    var newNote = e.editor.getData();

                    if (newNote === initializedNote || confirm(res.getRes('confirm_close_markup_note'))) {
                        closeEditor();
                    }
                }
            },
            startupFocus: true
        });

        enableMarkupDelete();
    }

    function destroyEditor(note) {
        ckEditorInstance.destroy(true);

        if (_.isString(note)) {
            $editor.val(note);
        }

        // Hide iPad keyboard
        if (window.isIOs) {
            $editor.focus().blur();
        }

        $editor.hide();
    }

    function updateListViewMarkupNote(note) {
        var showFullMarkupNotes = ePlus.user.getPreference('display', 'showFullMarkupNotes');

        CreateParagraphsForNotes(settings.sku, showFullMarkupNotes);

        var $note = getMarkupNote(),
            $noteToggle = getMarkupNoteToggle();

        if (_.isString(note)) {
            $note.html(note);
        }

        $note.show();

        markupNoteFormatting(settings.sku);

        $noteToggle.show();
    }

    function saveMarkupNote(editor) {
        if (isSaving()) return;

        setIsSaving(true);

        var MAX_NOTE_LENGTH = 53;

        if (!editor) {
            alert(res.getRes('error_unexpected'));
            return;
        }

        var note = editor.getData();

        updateSortRefineNoteMarkup(settings.sku, note.length, MAX_NOTE_LENGTH);
        getRefineNumbers();

        var callback = function () {
            setIsSaving(false);
        };

        if (note.length === 0) {
            deleteMarkupNoteAndCloseEditor(settings.mailingId, settings.sku, callback);
        } else if (originalNote.length > 0 && originalNote !== note) {
            updateMarkupNoteAndCloseEditor(settings.mailingId, settings.sku, note, callback);
        } else if (originalNote.length === 0) {
            createMarkupNoteAndCloseEditor(settings.mailingId, settings.sku, note, callback);
        } else {
            ckEditorInstance.fire('closebutton');
            callback();
        }
    }

    function createMarkupNoteAndCloseEditor(mailingId, sku, note, callback) {
        ePlus.modules.markups.createMarkupNote(mailingId, sku, note)
            .done(function (result) {
                var note = result && result.text || '';
                closeEditor(note);
            })
            .fail(ePlus.modules.markups.failureHandler)
            .always(callback);
    }

    function updateMarkupNoteAndCloseEditor(mailingId, sku, note, callback) {
        ePlus.modules.markups.updateMarkupNote(mailingId, sku, note)
            .done(function (result) {
                var note = result && result.text || '';
                closeEditor(note);
            })
            .fail(ePlus.modules.markups.failureHandler)
            .always(callback);
    }

    function deleteMarkupNoteAndCloseEditor(mailingId, sku, callback) {
        if (typeof callback !== 'function') { callback = function () { } };

        var isInlineEditor = ePlus.user.getPreference('markup', 'useInlineEditor') === 'true';
        var originalNote = isInlineEditor
            ? $('#markupNoteEditor_' + sku).val()
            : $('#mailingNote_' + sku).val();

        var successHandler = function () {
            if (isInlineEditor) {
                closeEditor('');
            } else {
                $('#markupNote' + sku).html('');
                $('#markupEditor_' + sku).trigger('close');
            }
        };

        if (originalNote.length > 0) {
            ePlus.modules.markups.deleteMarkupNote(mailingId, sku)
                .done(successHandler)
                .fail(ePlus.modules.markups.failureHandler)
                .always(callback);
        } else {
            successHandler();
            callback();
        }
    }

    function enableMarkupDelete() {
        $('#markup-note-delete-' + settings.sku).off().on('click', function () {
            var yes = getRes('yes');
            var no = getRes('no');
            var buttons = {};

            buttons[yes] = function () {
                window.ePlus.modules.inlineMarkupNoteEditor.deleteMarkupNoteAndCloseEditor(settings.mailingId, settings.sku);
                closeModal();
            };

            buttons[no] = function () {
                closeModal();
            };

            modalConfirm({
                message: getRes('confirm_delete_personal_note'),
                width: "340px",
                height: "150px",
                buttons: buttons
            });
        });
    }

    function closeEditor(note) {
        hideDeleteIcon();
        destroyEditor(note);
        updateListViewMarkupNote(note);
        deactivatePageBlocks();
    }

    function hideDeleteIcon() {
        $('#markup-note-delete-' + settings.sku).addClass('hidden');
    }

    function initialize(config) {
        settings = $.extend({}, defaults, config);

        editorId = 'markupNoteEditor_' + settings.sku;
        $editor = $('#' + editorId);
        clickSelector = 'a, button';
        clickEvent = 'click.' + editorId;
        beforeUnloadEvent = 'beforeunload.' + editorId;
        $('#markup-note-delete-' + settings.sku).removeClass('hidden');

        activatePageBlocks();
        initializeEditor();
    }

    // Public
    imne.initialize = initialize;
    imne.isSaving = isSaving;
    imne.setIsSaving = setIsSaving;
    imne.deleteMarkupNoteAndCloseEditor = deleteMarkupNoteAndCloseEditor;
})(window.ePlus.modules.inlineMarkupNoteEditor = window.ePlus.modules.inlineMarkupNoteEditor || {}, jQuery, _, CKEDITOR);;
(function (pc, $, _, undefined) {
    var products = {
            INSERT: 'Inserts',
            FEATURED_SPOT: 'Featured Spot',
            NEWSLETTER_BANNER: 'Newsletter Banner',
            NEWSLETTER_BANNER_CONTENT: 'Newsletter Banner Content',
            UK_NEWSLETTER_BANNER: 'UK Newsletter Banner',
            UK_NEWSLETTER_BANNER_CONTENT: 'UK Newsletter Banner Content'
        },
        defaults = {
            pluginId: 'payment-control-plugin',
            iframeId: 'payment-control-iframe',
            height: '100%',
            width: '100%',
            referenceIds: [],
            sku: null,
            promoCode: null,
            paymentPluginUri: 'https://payments.edelweiss.plus/Payments/Plugin',
            onDropinCreated: null,
            onDropinDestroyed: null,
            onRequestPaymentMethod: null,
            onPaymentMethodRequestable: null,
            onNoPaymentMethodRequestable: null,
            onClose: null,
            onSubmit: null,
            onSubmitDone: null,
            onSubmitFail: null
        },
        settings,
        eventHandlers = {};

    function getToken(callback) {
        $.ajax({
            type: 'POST',
            url: '/token',
            contentType: 'application/x-www-form-urlencoded',
            data: {
                grant_type: 'password'
            }
        }).always(function (data) {
            if (typeof callback === 'function') {
                callback(data.access_token);
            }
        });
    }

    function dispatchEvent(event, data) {
        var handler = 'on' + event.charAt(0).toUpperCase() + event.slice(1);

        if (typeof settings[handler] === 'function') {
            settings[handler](data);
        }
    }
    
    function encodeData(data) {
        if (!data) return;

        var json = JSON.stringify(data),
            base64EncodedJson = btoa(json);

        return base64EncodedJson;
    }

    function buildForm(token, base64EncodedJson) {
        var $form = $('<form />', {
            action: settings.paymentPluginUri,
            target: settings.iframeId,
            method: 'POST',
            css: {
                display: 'none'
            }
        });

        $form.append(
            $('<input />', {
                type: 'hidden',
                name: 't',
                value: token
            }),
            $('<input />', {
                type: 'hidden',
                name: 'base64EncodedJson',
                value: base64EncodedJson
            })
        );

        return $form;
    }

    function buildIframe() {
        return $('<iframe />', {
            id: settings.iframeId,
            name: settings.iframeId,
            css: {
                width: settings.width,
                height: settings.height,
                border: 0
            }
        });
    }
        
    function initializeIframeMessageEventHandler() {
        // Setup event handler to listen to messages from TreelinePayments
        var eventName = 'message.' + settings.pluginId;

        $(window).off(eventName).on(eventName, function (e) {
            // Only listen to messages from paymentPluginUri and that aren't from the Braintree drop-in
            if (!e || !e.originalEvent ||
                !e.originalEvent.origin ||
                !e.originalEvent.data ||
                settings.paymentPluginUri.indexOf(e.originalEvent.origin) === -1 ||
                e.originalEvent.data.indexOf('braintree') > 0) return;

            var message = JSON.parse(e.originalEvent.data);

            if (message.event) {
                dispatchEvent(message.event, message.data);
            }
        });
    }

    function initializeIframe() {
        getToken(function (token) {
            var $plugin = $('#' + settings.pluginId);

            if (!token || $plugin.length === 0) {
                var message = 'Error initializing payment control!';

                alert(message);
                console.error(message);

                return;
            }

            var data = encodeData({
                    referenceIds: settings.referenceIds,
                    sku: settings.sku,
                    promoCode: settings.promoCode
                }),
                $form = buildForm(token, data),
                $iframe = buildIframe();

            initializeIframeMessageEventHandler();
            
            $iframe.appendTo($plugin);
            $form.appendTo(document.body);
            $form.submit().remove();
        });
    }

    function initialize(config) {
        settings = $.extend({}, defaults, config);

        initializeIframe();
    }

    function setDefaults(config) {
        defaults = $.extend({}, defaults, config);
    }

    pc.products = products;
    pc.initialize = initialize;
    pc.setDefaults = setDefaults;
})(window.ePlus.modules.paymentControl = window.ePlus.modules.paymentControl || {}, jQuery, _);;
; ePlus.modules.addDrc = (function () {
    var setUploadStatus = function (fileName, message) {
        $("#fileName").html(fileName + " - " + message);
    }

    var getFileUploadApiEndpoint = function () {
        var selectedProfile = $("#profileSelector option:selected").val();
        var url = '/api/galleys/import/profiles/' + selectedProfile + '/files';

        return url;
    }

    var buildFileResultsHtml = function (fileResults) {
        if (!fileResults) return;

        var $fileResults = $('<div/>');

        $.each(fileResults, function (i, fileResult) {
            var $fileResult = $('<p/>', {
                html: getFileResultStatus(fileResult)
            });

            $fileResult.append(buildFileResultsHtmlList(fileResult.messages));
            $fileResult.append(buildFileResultsHtmlList(fileResult.warnings));
            $fileResult.append(buildFileResultsHtmlList(fileResult.errors));

            $fileResults.append($fileResult);
        });

        return $fileResults;
    }

    function getFileResultStatus(fileResult) {
        return fileResult.fileName + ' - ' +
            (fileResult.isSuccessful ?
                '<span class="fileResultSuccess"><%= GetLocalizedJavascriptString("success").ToUpper() %></span>' :
                '<span class="fileResultFailure"><%= GetLocalizedJavascriptString("failed").ToUpper() %></span>');
    }

    function buildFileResultsHtmlList(fileResultList) {
        if (!fileResultList) return;

        var $fileResultList = $('<ul/>', {
            class: 'fileResults'
        });

        $.each(fileResultList, function (i, fileResultListItem) {
            var $fileResultListItem = $('<li/>', {
                text: fileResultListItem
            });

            $fileResultList.append($fileResultListItem);
        });

        return $fileResultList;
    }

    return {
        setUploadStatus: setUploadStatus,
        getFileUploadApiEndpoint: getFileUploadApiEndpoint,
        buildFileResultsHtml: buildFileResultsHtml
    }
})();;
; ePlus.modules.listViewTitleRow = (function () {
    var res = window.ePlus.resources;
    var doAddTitleBlocker = false;

    var addTitleContentEditedBlocker = function (sku) {
        if (!doAddTitleBlocker) return;

        if ($(".title-content-blocker", $("#ltRow" + sku)).length === 0) {
            var editTitle = _.template('<span class="icon-preferences-icon columnSpaced editTitle iconTitleEdit" onclick=openEditTitle("{{sku}}");><span>');
            $("#ltRow" + sku).wrapInner('<div class="title-content-blocker"></div>').prepend('<div class="title-content-text">' + res.getRes('content_edited') + editTitle({ 'sku': sku}) + '</div>');
        }

        doAddTitleBlocker = false;
    }

    var setTitleBlockerStatus = function (status) {
        doAddTitleBlocker = status;
    }

    var getTitleBlockerStatus = function () {
        return doAddTitleBlocker;
    }
    return {
        addTitleContentEditedBlocker: addTitleContentEditedBlocker,
        setTitleBlockerStatus: setTitleBlockerStatus,
        getTitleBlockerStatus: getTitleBlockerStatus
    }
})();;
(function (mmc, $, _, CKEDITOR, undefined) {
    var res = window.ePlus.resources;
    var settings, $form;

    var marketingCollectionType = {
        SHOWCASE: 'showcase',
        HEADER: 'header'
    };

    function editMarketingCollectionTitles(catalogId, collectionId) {
        $('#editTitleDiv').trigger('close');

        openEditCatalog(collectionId, settings.resultType, {
            onClose: function () {
                openEditCatalog(catalogId, settings.resultType);
            }
        });
    }

    function saveMarketingCollection(mc, doEdit) {
        var saving = res.getRes('saving');
        savingModal(saving);
        savingModalOverlay(saving, 'productAdminGrid');

        $.ajax({
            url: '/api/marketingCollections',
            type: mc.id ? 'PUT' : 'POST',
            data: mc
        }).done(function (data) {
            closeSavingModal();
            closeSavingModalOverlay('productAdminGrid');
            closeModal();
            $('#editTitleDiv').trigger('hideBlock');

            if (data.type === marketingCollectionType.SHOWCASE && doEdit) {
                editMarketingCollectionTitles(data.catalogId, data.collectionId);
            } else {
                openEditCatalog(data.catalogId, settings.resultType);
            }
        }).fail(function (jqXHR) {
            closeSavingModal();
            closeSavingModalOverlay('productAdminGrid');

            if (!jqXHR.responseText) return;

            var response = JSON.parse(jqXHR.responseText);

            if (!response.message) return;

            alert(response.message);    
        });
    }

    function deleteMarketingCollection(mc) {
        var deleting = res.getRes('deleting');
        savingModal(deleting);
        savingModalOverlay(deleting, 'productAdminGrid');

        $.ajax({
            url: '/api/marketingCollections/' + mc.id,
            type: 'DELETE'
        }).done(function () {
            closeSavingModal();
            closeSavingModalOverlay('productAdminGrid');
            closeModal();
            $('#editTitleDiv').trigger('hideBlock');
            openEditCatalog(mc.catalogId, settings.resultType);
        }).fail(function (x, y, z) {
            closeSavingModal();
            closeSavingModalOverlay('productAdminGrid');

            if (!jqXHR.responseText) return;

            var response = JSON.parse(jqXHR.responseText);

            if (!response.message) return;

            alert(response.message);   
        });
    }

    function serializeFormAsJson(form) {
        return $form.serializeJSON({
            parseNumbers: true,
            parseBooleans: true
        });
    }

    function initializeAutoComplete() {
        $.ajax({
            url: '/api/catalogs/' + settings.catalogId + '/products',
            type: 'GET'
        }).done(function (data) {
            if (!data || !data.products) return;

            $('input[name="sku:string"]', $form).autocomplete(data.products, {
                matchContains: true,
                mustMatch: true,
                max: 100,
                formatItem: function (i) {
                    return i.ean + ' - ' + i.fullName;
                },
                formatResult: function (i) {
                    return i.ean;
                }
            }).on('focus', function () {
                $(this).select();
            });
        }).fail(function () {
            alert('Error');
            closeModal();
            $('#editTitleDiv').trigger('hideBlock');
        });
    }
    
    function initializeForm() {
        $('#editTitleDiv').trigger('showBlock');

        $form = $('#manageMarketingCollection');

        $('button', $form).on('click', function (e) {
            e.preventDefault();

            var $this = $(this);

            if ($this.hasClass('save')) {
                var data = {
                    doEdit: !!$this.data('doedit')
                };
                $form.trigger('submit', data);
            } else if ($this.hasClass('delete')) {
                var mc = serializeFormAsJson();
                deleteMarketingCollection(mc);
            } else {
                closeModal();
                $('#editTitleDiv').trigger('hideBlock');
            }
        });
        
        $form.on('submit', function (e, data) {
            e.preventDefault();
            var mc = serializeFormAsJson();
            saveMarketingCollection(mc, data && data.doEdit);
        });

        initializeAutoComplete();
    }

    function initialize(config) {
        settings = config;
        initializeForm();
    }

    mmc.initialize = initialize;
})(window.ePlus.modules.manageMarketingCollection = window.ePlus.modules.manageMarketingCollection || {}, jQuery, _, CKEDITOR);;
; ePlus.modules.goodreads = (function () {
    var getGoodReadsTitleReview = function (sku) {
        return $.ajax({
            type: "GET",
            url: "api/" + sku + "/reviews/goodreads",
            contentType: "application/json",
        });
    }

    var postReviewToGoodReads = function (review) {
        $.ajax({
            type: "POST",
            url: "api/reviews/goodreads",
            data: JSON.stringify(review),
            contentType: "application/json",
        });
    }

    var updateGoodReadsReview = function (review) {
        $.ajax({
            type: "PUT",
            url: "api/reviews/goodreads",
            data: JSON.stringify(review),
            contentType: "application/json",
        });
    }

    return {
        postReviewToGoodReads: postReviewToGoodReads,
        updateGoodReadsReview: updateGoodReadsReview,
        getGoodReadsTitleReview: getGoodReadsTitleReview
    }
})();;
; (function (ie, $, _, CKEDITOR, undefined) {
    var defaults = {
        width: '100%',
        height: 200,
        editorId: 'editor_',
        namespace: 'editor',
        activeEditorNamespace: 'editor',
        editorTextContainer: 'editorTextContainer_',
        eventContainer: 'editorEventContainer_',
        onSave: null,
        onClose: null,
        onDelete: null,
        enableEditor: null
    },
        settings,
        $editor,
        ckEditorInstance,
        clickSelector,
        clickEvent,
        beforeUnloadEvent;

    function getEditorId() {
        return settings.editorId;
    }

    function getEditor() {
        var editorId = getEditorId();
        return $('#' + editorId);
    }

    function getEditorTextContainer() {
        return $('#' + settings.editorTextContainer);
    }

    function getEditorEventContainer() {
        var eventContainerId = settings.eventContainer;
        return $('#' + eventContainerId);
    }

    function activatePageBlocks() {
        disableEditor(settings.sku);
        $('#autoComplete').prop('disabled', true); // Prevent interaction with jump-to autocomplete
        WebuiPopovers.disableAll();                // Prevent interaction with popovers

        var message = getRes('inline_editor_close_warning');

        // In addition to blocking the markup bar, we also want to capture any
        // DOM events that should trigger closing the inline editor.
        // If the user triggers any of the events below, we want to prompt
        // the user to let them know that continuing with the action will close
        // the editor dialog without saving changes.

        // 1.) Page interactions (i.e. opening dialog boxes or hashchange events)
        window.interactionHandler = function (e, callback, cancelCallback) {
            if (confirm(message)) {
                closeEditor();

                if (typeof callback === 'function') {
                    callback(e);
                }
            } else if (typeof cancelCallback === 'function') {
                cancelCallback();
            }
        }

        // 2.) Clicking links/buttons on the page
        $('#interiorPageContent').on(clickEvent, clickSelector, function (e) {
            var $self = $(this);

            // If this is a click inside the CKEditor, pass it through
            if ($self.parents('.cke').length) {
                return true;
            }

            if (e) {
                e.preventDefault();
                e.stopPropagation();
            }

            // Leverage the interaction handler above
            $(window).trigger('interaction', [function () {
                $self[0].click();
            }]);
        });

        // 3.) Reload/navigate away from the page
        $(window).off(beforeUnloadEvent).on(beforeUnloadEvent, function (e) {
            e = e || window.event;
            e.returnValue = message;

            return message; // Most modern browsers will ignore this due to scammers, but it doesn't hurt
        });
    }

    function deactivatePageBlocks() {
        $(window).off(beforeUnloadEvent);
        window.interactionHandler = null;
        $('#interiorPageContent').off(clickEvent, clickSelector);
        $('#autoComplete').prop('disabled', false);
        WebuiPopovers.enableAll();
        setTimeout(function () {
            settings.enableEditor(settings.sku);
        }, 250);
        setListViewProperty(settings.activeEditorNamespace, null);
    }

    function disableEditor(sku) {
        var $elem = getEditorEventContainer();

        $elem.off('click.' + settings.namespace);
    }

    function initializeEditor(saveText) {
        var $textContainer = getEditorTextContainer();
        var initializedNote;

        $textContainer.hide();
        $editor.show();

        ckEditorInstance = CKEDITOR.replace(getEditorId(), {
            language: ePlus.user.culture,
            disableNativeSpellChecker: false,
            width: settings.width,
            height: settings.height,
            extraPlugins: 'ajaxsave,ajaxdelete,closebutton,lineutils,widget,image2',
            toolbar: [
                { name: 'save', items: ['AjaxSave'] },
                { name: 'delete', items: ['AjaxDelete']},
                { name: 'close', items: ['CloseButton'] },
                { name: 'font', items: ['FontSize'] },
                { name: 'styles', items: ['Bold', 'Italic', 'Underline', 'Strike', 'RemoveFormat'] },
                { name: 'format', items: ['NumberedList', 'BulletedList'] },
                { name: 'color', items: ['TextColor', 'BGColor'] },
                { name: 'links', items: ['Image', 'Link', 'Unlink'] }
            ],
            on: {
                instanceReady: function (e) {
                    e.editor.focus();
                    initializedNote = e.editor.getData();
                },
                ajaxsave: function (e) {
                    if (typeof settings.onSave === 'function') {
                        settings.onSave(e.editor);
                        closeEditor();
                    }
                },
                ajaxdelete: function (e) {
                    if (typeof settings.onDelete === 'function') {
                        settings.onDelete(e.editor, closeEditor);
                    }
                },
                closebutton: function (e) {
                    var newNote = e.editor.getData();

                    if (newNote === initializedNote || confirm(getRes('confirm_close_markup_note'))) {
                        closeEditor();
                        event.stopPropagation();
                    }
                }
            },
            startupFocus: true
        });
    }

    function destroyEditor(note) {
        ckEditorInstance.destroy(true);

        if (_.isString(note)) {
            $editor.val(note);
        }

        // Hide iPad keyboard
        if (window.isIOs) {
            $editor.focus().blur();
        }

        $editor.hide();
    }

    function closeEditor(note) {
        destroyEditor(note);
        deactivatePageBlocks();
        $textContainer = getEditorTextContainer();
        $textContainer.show();

        if (typeof settings.onSave === 'function') {
            settings.onClose();
        }
    }

    function initialize(config) {
        settings = $.extend({}, defaults, config);

        $editor = $('#' + getEditorId());
        clickSelector = 'a, button';
        clickEvent = 'click.' + settings.namespace;
        beforeUnloadEvent = 'beforeunload.' + settings.namespace;
        
        activatePageBlocks();
        initializeEditor();

        if (settings.bindDelete) {
            settings.bindDelete(ckEditorInstance, settings.sku);
        }
    }

    // Public
    ie.initialize = initialize
})(window.ePlus.modules.inlineEditor = window.ePlus.modules.inlineEditor || {}, jQuery, _, CKEDITOR);;
/**
 * @description Orders JS library for Edelweiss+
 * Dependencies: window.orderBackbone, window.sortrefine, window.buyingStores
 */
; var o = (function () {
    /** Number used by certain POS systems. Value overrides quantity validity checks. */
    var IBIDIE_INVENTORY_NUMBER = -1;
    var FILTER_TYPE_PUB_ORDERS = "90";
    var FILTER_TYPE_SUGGESTIONS = "80";
    var FILTER_TYPE_PUB_SUGGESTIONS = "81";
    var isPublisher = false;
    var unitView = {
        ORDER: 'order',
        SUGGESTION: 'suggestion'
    }
    var api = {};
    var translations = {};

    /**
     * Initializes the Edelweiss ordering Javascript module
     * @param {Object} options - Options to initialize the ordering module
     * @param {bool} options.isPublisher - Whether the current user is in a publisher org
     * @param {bool} options.isStoreMultiLineMode - Whether the current user's POS support multiple line items per store/sku
     * @param {Object} options.translations - Object containing translation localization keys and values
     */
    function init(options) {
        isPublisher = options.isPublisher;
        api.isStoreMultiLineMode = options.isStoreMultiLineMode || false;
        api.selectorPrefix = "#";
        translations = options.translations || {};
    }

    function setStoreMultiLineMode(isStoreMultiMode) {
        api.isStoreMultiLineMode = isStoreMultiMode || false;
    }

    /**
     * General utility functions for Edelweiss+ ordering.
     */
    var utils = function () {
        /**
         * Checks for the existence of editable ordering elements on the page.
         * While orders may be editable, updates to orders may be disabled.
         * @returns {boolean}
         */
        function areOrdersEditable() {
            var isOwnOrder = true;
            var $orderSelect = $("#order_" + getSelectedOrderId());
            if (isPublisher && (
                ($orderSelect.length && !$orderSelect.data("is-my-order")) ||
                // Need to confirm that window.listView.isMyOrder is a Boolean and initialized
                (getListViewProperty("isMyOrder") === false))) {
                isOwnOrder = false;
            }
            return ($(".multiStoreOrdering").length > 0 || $(".orderingControlContainer").length > 0) && isOwnOrder
                && getListViewProperty("canEditOrders") !== false;
        }
        /**
         * Creates jQuery selector to easily select order input line item category elements.
         * @param {string} storeId
         * @param {string} sku
         * @returns {jQuery Selector}
         */
        function buildStoreCategorySelector(storeId, sku, categoryNumber) {
            return $(".catLoc_" + storeId + "_" + sku + "_" + categoryNumber);
        }
        /**
         * Creates jQuery selector to easily select order input line item category elements.
         * @param {string} storeId
         * @param {string} sku
         * @returns {jQuery Selector}
         */
        function buildStoreCategoryContainerSelector(storeId, sku, categoryNumber) {
            return $(".catLocDisplay_" + storeId + "_" + sku + "_" + categoryNumber);
        }

        /**
         * Creates jQuery selector to easily select order input line item category elements.
         * @param {string} storeId
         * @param {string} sku
         * @param {number} lineItem
         * @returns {jQuery Selector}
         */
        function buildLineItemCategorySelector(storeId, sku, lineItem, categoryNumber) {
            return $(".catLoc_" + storeId + "_" + sku + "_" + lineItem + "_" + categoryNumber);
        }

        /**
         * Creates jQuery selector to easily select the line item container for a given store.
         * @param {string} storeId
         * @param {string} sku
         * @returns {jQuery Selector}
         */
        function buildLineItemStoreContainerSelector(storeId, sku) {
            return $(api.selectorPrefix + "orderStore_" + storeId + "_" + sku);
        }

        /**
         * Creates jQuery selector to easily select the line item container for a given line item.
         * @param {string} storeId
         * @param {string} sku
         * @param {number} lineItem
         * @returns {jQuery Selector}
         */
        function buildLineItemsContainerSelector(storeId, sku, lineItem) {
            return $(api.selectorPrefix + "lineItems_" + sku + "_" + storeId + "_" + lineItem);
        }

        /**
         * Creates jQuery selector to easily select order input elements related to params.#
         * @param {string} storeId
         * @param {string} sku
         * @returns {jQuery Selector}
         */
        function buildInputSelector(storeId, sku) {
            return $(api.selectorPrefix + "inputOrders_" + storeId + "_" + sku);
        }

        /**
         * Creates jQuery selector to easily select the line item input for a given line item.
         * @param {string} storeId
         * @param {string} sku
         * @param {number} lineItem
         * @returns {jQuery Selector}
         */
        function buildLineItemInputSelector(storeId, sku, lineItem) {
            return $(api.selectorPrefix + "inputOrders_" + storeId + "_" + sku + "_" + lineItem);
        }

        /**
         * Creates jQuery selector to easily select the total POS on-hand element related to params.
         * @param {string} storeId
         * @param {string} sku
         * @returns {jQuery Selector}
         */
        function buildPosOhSelector(storeId, sku) {
            return $(api.selectorPrefix + "posOH_" + storeId + "_" + sku);
        }

        /**
         * Creates jQuery selector to easily select the total POS on-order element related to params.
         * @param {string} storeId
         * @param {string} sku
         * @returns {jQuery Selector}
         */
        function buildPosOoSelector(storeId, sku) {
            return $(api.selectorPrefix + "posOO_" + storeId + "_" + sku);
        }

        /**
         * Get the currently selected order Id (if it exists)
         * @returns {number} Order Id
         */
        function getSelectedOrderId() {
            var orderId = getListViewProperty("selectedOrderID");
            if (_.isNil(orderId)) {
                orderId = $("#availableOrdersModal").attr("val");
            }

            return !_.isNaN(_.parseInt(orderId, 10)) ? _.parseInt(orderId, 10) : "";
        }

        /**
         * Checks if line item element exists in the DOM.
         * @param {string} storeId
         * @param {string} sku
         * @param {number} lineItem
         * @returns {boolean}
         */
        function doesLineItemExist(storeId, sku, lineItem) {
            return buildLineItemInputSelector(storeId, sku, lineItem).length > 0;
        }

        /**
         * Get selected store options (if they exist) for reps or retailers
         * @returns {array} Store Ids
         */
        function getSelectedStoreIds() {
            var storeIds = [];

            if (isPublisher || (!isPublisher && $(".storeOptionCheck", $("#footerBar")).length === 0)) {
                storeIds = _.map(window.buyingStores, function (so) { return so.storeID || so.StoreID });
                var $selectedAccount = $("#selectedAccountName");
                if (_.size(storeIds) === 0 && (_.isNil($selectedAccount) ||
                    (!_.isNil($selectedAccount) && (_.isNil($selectedAccount.html()) || $selectedAccount.html().trim() === "")))) {
                    storeIds.push("1"); //hard-coded value used in code behind to denote order with no account
                }
            } else {
                var $target = $(".storeOptionCheck.box_checked", $("#footerBar"));
                $target.each(function() { storeIds.push($(this).attr("storeID")) });
            }

            return storeIds;
        }

        /**
         * Get unit view type for reps (suggestions or orders)
         * @returns {string} Unit view type
         */
        function getUnitsViewType() {
            if (view.isSkuModal()) return unitView.ORDER;
            return !_.isNil(window.listView) ? getListViewProperty("unitsViewType") : "";
        }

        return {
            areOrdersEditable: areOrdersEditable,
            buildInputSelector: buildInputSelector,
            buildLineItemCategorySelector: buildLineItemCategorySelector,
            buildLineItemInputSelector: buildLineItemInputSelector,
            buildLineItemStoreContainerSelector: buildLineItemStoreContainerSelector,
            buildLineItemsContainerSelector: buildLineItemsContainerSelector,
            buildPosOhSelector: buildPosOhSelector,
            buildPosOoSelector: buildPosOoSelector,
            buildStoreCategorySelector: buildStoreCategorySelector,
            buildStoreCategoryContainerSelector: buildStoreCategoryContainerSelector,
            doesLineItemExist: doesLineItemExist,
            getSelectedOrderId: getSelectedOrderId,
            getSelectedStoreIds: getSelectedStoreIds,
            getUnitsViewType: getUnitsViewType
        }
    }();

    /**
     * Orders view contains functionality to easily update order elements (if they exist)
     */
    var view = function () {
        /**
         * Adds line item element based on data provided.
         * @param {string} sku
         * @param {string} storeId
         * @param {number} lineItem
         * @param {string/number} units
         * @param {string} category1
         * @param {string} category2
         */
        function addLineItem(sku, storeId, lineItem, units, category1, category2) {
            var lineItemHtml = templateCache.getNewLineItemInput(sku, storeId, lineItem, units, translations['delete_store_line_item'], [category1, category2], translations['edit_category'], translations['must_select_order']);
            utils.buildLineItemStoreContainerSelector(storeId, sku).append(lineItemHtml);
            enableLineItemPopover(sku, storeId, lineItem);
        }

        /**
         * Checks for the existence of the argument sku on other orders.
         * @param {string} sku
         * @returns {boolean}
         */
        function areUnitsOnOtherOrder(sku) {
            var selectedOrder = utils.getSelectedOrderId();
            return _.some(window.orderBackbone[sku], function (ol) {
                return ol.orderID !== selectedOrder &&
                    (_.parseInt(ol.units, 10) > 0);
            });
        }

        /**
         * Clear input order input elements related to the currently selected sku
         * @param {string} sku
         */
        function clearSkuValue(sku) {
            $(".inputOrder_" + sku, $(api.selectorPrefix + "msDiv_" + sku)).val("").attr("data-savedval", "");
        }

        /**
         * Enables and disables order inputs based on validity of order id.
         * Certain views disallow order editing (such as all orders view)
         * @param {string} sku
         */
        function toggleOrderEdit(sku) {
            var selectedOrderId = utils.getSelectedOrderId();
            $(".totalOrderBox", $(api.selectorPrefix + "msDiv_" + sku)).prop("disabled", true);
            if (utils.areOrdersEditable() && selectedOrderId !== 0 && selectedOrderId !== -1) {
                toggleStoreInputEdit(sku);
            } else {
                $(".multiOrderBox, .lineItemOrderBox", $(api.selectorPrefix + "msDiv_" + sku)).prop("disabled", true).addClass("bgdDarkGray non-clickable").removeClass("clickable");
            }
        }

        /**
         * Disables store inputs if line items exist.
         * @param {string} sku
         */
        function toggleStoreInputEdit(sku) {
            _.forEach(utils.getSelectedStoreIds(), function(storeId) {
                utils.buildInputSelector(storeId, sku).prop("disabled", false).removeClass("bgdDarkGray non-clickable").addClass("clickable");
            });
            _.forEach(getValidLineItems(sku), function(ol) {
                var $skuStoreInput = utils.buildInputSelector(ol.storeID, sku);
                if (utils.doesLineItemExist(ol.storeID, sku, ol.lineItem)) {
                    $skuStoreInput.prop("disabled", true).addClass("bgdDarkGray non-clickable").removeClass("clickable");
                }
            });
        }

        /**
         * Remove all line item elements from DOM
         */
        function removeAllLineItems() {
            $(".lineItemsContainer").remove();
        }

        /**
         * Set POS on-hand and on-order totals for total input boxes.
         * @param {string} sku
         */
        function setPosOhOoTotals(sku) {
            var stores = utils.getSelectedStoreIds();
            if (_.size(stores) > 1) {
                var posOh = 0, posOo = 0, temp;
                _.forEach(utils.getSelectedStoreIds(), function (storeId) {
                    temp = _.parseInt(utils.buildPosOhSelector(storeId, sku).html(), 10);
                    if (!_.isNaN(temp) && temp > 0) {
                        posOh += temp;
                    }
                    temp = _.parseInt(utils.buildPosOoSelector(storeId, sku).html(), 10);
                    if (!_.isNaN(temp) && temp > 0) {
                        posOo += temp;
                    }
                });

                var $posDisplayTarget = $("#posDisplay_" + sku);
                var $posOhTarget = $(api.selectorPrefix + "posOH_" + sku);
                var $posOoTarget = $(api.selectorPrefix + "posOO_" + sku);
                if (posOh + posOo > 0) {
                    $(api.selectorPrefix + "pos_" + sku).html(posOh + posOo);
                    $posDisplayTarget.show();
                } else {
                    $posDisplayTarget.hide();
                }
                posOh > 0 ? $posOhTarget.html(posOh).show() : $posOhTarget.html("").hide();
                posOo > 0 ? $posOoTarget.html(posOo).show() : $posOoTarget.html("").hide();
            }
        }

        /**
         * Toggles the UI element to indicate that a sku has values on another order.
         * @param {string} sku
         * @param {boolean} onOtherOrder
         */
        function setSkuOnOtherOrder(sku, onOtherOrder) {
            var $exclamIconTarget = $(api.selectorPrefix + "exclamIcon_" + sku);
            if (onOtherOrder) {
                $exclamIconTarget.show();
            } else {
                $exclamIconTarget.hide();
            }
        }

        /**
         * Update sku order total boxes.
         * @param {string} sku
         */
        function setSkuTotals(sku) {
            var sum = null, temp, tempInt;
            _.forEach(utils.getSelectedStoreIds(), function (storeId) {
                temp = utils.buildInputSelector(storeId, sku).val();
                tempInt = _.parseInt(temp, 10);
                if (temp !== "" && tempInt >= 0) {
                    !_.isNil(sum) ? sum += tempInt : sum = tempInt;
                }
            });
            sum = !_.isNil(sum) && !_.isNaN(sum) ? sum : "";
            $(api.selectorPrefix + "inputOrders_" + sku).val(sum).data("savedval", sum);
            if (sum > 0) {
                $("#skuModalOrderCount").html(sum).show();
            } else {
                $("#skuModalOrderCount").html("").hide();
            }
        }

        /**
         * Reads window.orderBackbone to update sku element values.
         * @param {string} sku
         */
        function setSkuValues(sku) {
            $(".addStoreLineItem_" + sku, $(api.selectorPrefix + "msDiv_" + sku)).hide();

            var storeLineItemCountMap = {};
            _.forEach(utils.getSelectedStoreIds(), function(storeId) {
                storeLineItemCountMap[storeId] = _.size(getLineItems(sku, storeId));
            });

            _.forEach(getValidLineItems(sku), function (ol) {
                var qty = _.isNaN(_.parseInt(ol.units, 10)) ? "" : _.parseInt(ol.units, 10);

                if (qty !== "") {
                    var $storeInput = utils.buildInputSelector(ol.storeID, sku);
                    var storeTotal = qty;
                    if (qty !== IBIDIE_INVENTORY_NUMBER) {
                        var existingTotal = _.parseInt($storeInput.val(), 10) || 0;
                        if (!_.isNaN(existingTotal) && existingTotal >= 0) storeTotal += existingTotal;
                    }

                    $storeInput.val(storeTotal).attr("data-savedval", storeTotal);
                    if ((storeTotal >= 0 || storeTotal === IBIDIE_INVENTORY_NUMBER) &&
                        utils.getSelectedOrderId() !== -1 && utils.getSelectedOrderId() !== 0) {
                        if (api.isStoreMultiLineMode) {
                            $(api.selectorPrefix + "addStoreLineItem_" + ol.storeID + "_" + sku).show();
                        }
                    } else {
                        $(api.selectorPrefix + "addStoreLineItem_" + ol.storeID + "_" + sku).hide();
                    }
                }

                if (utils.doesLineItemExist(ol.storeID, sku, ol.lineItem)) {
                    var $lineItem = utils.buildLineItemInputSelector(ol.storeID, sku, ol.lineItem);
                    $lineItem.val(qty).attr("data-savedval", qty);
                } else if (storeLineItemCountMap[ol.storeID] > 1) {
                    addLineItem(sku, ol.storeID, ol.lineItem, ol.units, ol.category || "", ol.category2 || "");
                }
            });
        }

        /**
         * Display input element as saving
         * @param {DOM Object} elem
         */
        function toggleSaving(elem) {
            $(elem).toggleClass("unSavedUnits");
        }

        function toggleSkuSaving(sku) {
            var $inputs = $('.inputOrder_' + sku);

            $inputs.each(function () {
                var disabled = $(this).prop('disabled');

                $(this).prop('disabled', !disabled);
            });

            toggleSaving($inputs);
        }

        /**
         * Updates order, suggested, and on-hand values related to the given sku
         * @param {string} sku
         * @returns {boolean} Returns true if sku exists in orderBackbone
         */
        function updateSku(sku) {
            if (utils.getUnitsViewType() !== unitView.ORDER) return true;
            clearSkuValue(sku);
            setSkuValues(sku);
            setSkuTotals(sku);
            setSkuOnOtherOrder(sku, areUnitsOnOtherOrder(sku));
            setPosOhOoTotals(sku);
            toggleOrderEdit(sku);

            return true;
        }

        function updateSkuSuggestionTotals(sku) {
            var sum = null, temp, tempInt;
            _.forEach(o.utils.getSelectedStoreIds(), function (storeId) {
                temp = utils.buildInputSelector(storeId, sku).val();
                tempInt = _.parseInt(temp, 10);
                if (temp !== "" && tempInt >= 0) {
                    !_.isNil(sum) ? sum += tempInt : sum = tempInt;
                }
            });
            sum = !_.isNil(sum) && !_.isNaN(sum) ? sum : "";
            $(".inputOrders_" + sku).val(sum).data("savedval", sum);
        }

        function updateSuggestions(suggestions) {
            suggestions.forEach(function (suggestion) {
                var $input = utils.buildInputSelector(suggestion.storeId, suggestion.sku);

                $input.val('').attr('data-savedval', '');

                updateSkuSuggestionTotals(suggestion.sku);
                window.ePlus.modules.suggestions.getSuggestionTotals(suggestion);
                updateOrderSuggestionFilters(suggestion.sku, FILTER_TYPE_PUB_ORDERS);
            });

            populateSuggestionRefine(FILTER_TYPE_PUB_SUGGESTIONS);
        }

        function isSkuModal() {
            return api.selectorPrefix === '.';
        }

        return {
            addLineItem: addLineItem,
            toggleOrderEdit: toggleOrderEdit,
            toggleSaving: toggleSaving,
            toggleSkuSaving: toggleSkuSaving,
            removeAllLineItems: removeAllLineItems,
            updateSku: updateSku,
            updateSkuSuggestionTotals: updateSkuSuggestionTotals,
            updateSuggestions: updateSuggestions,
            isSkuModal: isSkuModal
        }
    }();

    /**
     * Monitor input update events for shortcuts to saving or escaping.
     * ESC - escapes
     * ENTER - saves
     * @param {DOM Object} elem
     * @param {Object} event      */
    function checkKey(elem, event) {
        if (!event) return false;
        var $e = $(elem), originalUnits = $e.attr("data-savedval");

        switch (event.which) {
            case 27: // ESC
                $e.val(originalUnits).blur();
                return false;
            case 13: // ENTER
                $e.blur();
                return false;
        }
        return false;
    }

    /**
     * Check if current order is modifiable.
     * @param {string} errorMessage
     */
    function checkOrder(errorMessage) {
        var selectedOrder = utils.getSelectedOrderId();
        if (selectedOrder === 0 || selectedOrder === -1) {
            modalAlert(errorMessage);
        }
    }

    function disableAddLineItemButtonsForSku(sku) {
        var selectedOrder = utils.getSelectedOrderId();

        if (selectedOrder === 0 || selectedOrder === -1) {
            return;
        }

        var storeIds = getSelectedStoreIds();

        _.forEach(storeIds, function (storeId) {
            disableAddLineItemButton(storeId, sku);
        });
    }

    function enableAddLineItemButtonsForSku(sku) {
        var selectedOrder = utils.getSelectedOrderId();

        if (selectedOrder === 0 || selectedOrder === -1) {
            return;
        }

        var storeIds = getSelectedStoreIds();

        _.forEach(storeIds, function (storeId) {
            enableAddLineItemButton(storeId, sku);
        });
    }

    function buildAddLineItemButtonSelector(storeId, sku) {
        return $(api.selectorPrefix + "addStoreLineItem_" + storeId + "_" + sku);
    }

    function disableAddLineItemButton(storeId, sku) {
        buildAddLineItemButtonSelector(storeId, sku).addClass('disabled');
    }

    function enableAddLineItemButton(storeId, sku) {
        buildAddLineItemButtonSelector(storeId, sku).removeClass('disabled');
    }

    /**
     * Updates input box if saving error or no change in input
     * @param {DOM Object} elem
     * @param {string} units
     * @param {string} originalUnits
     * @param {boolean} isSavingError
     */
    function cleanupInput(elem, units, originalUnits, isSavingError) {
        if (units === "" && !isSavingError) {
            $(elem).val(""); //Clear out undetected characters such as '-'
        } else if (units === originalUnits || isSavingError) {
            $(elem).val(originalUnits);
        }
    }

    function enableLineItemPopover(sku, storeId, lineItem) {
        $("#catLocDiv_" + storeId + "_" + sku + "_" + lineItem).webuiPopover({
            type: 'async',
            cache: false,
            url: "/GetTreelineControl.aspx?controlName=/uc/controls/OrderingControlCatSelect.ascx&sku=" + sku + "&storeID=" + storeId + (_.isNil(lineItem) ? "" : "&lineItem=" + lineItem),
            container: '#pageContent',
            async: {
                success: function () {
                    window.ActivateLineItemDept(sku, storeId, lineItem);
                }
            }
        });
    }

    /**
     * Get order line item objects
     * @param {string} sku
     * @param {string} storeId
     * @returns {Array<Object>}
     */
    function getLineItems(sku, storeId) {
        var selectedOrder = utils.getSelectedOrderId();
        return _.filter(window.orderBackbone[sku], function(ol) {
            return ol.orderID === selectedOrder && ol.storeID === storeId;
        });
    }

    /**
     * Returns the line item for the associated sku, storeId, on the currently selected order.
     * @param {string} sku
     * @param {string} storeId
     * @returns {int}
     */
    function getLineItemValue(sku, storeId) {
        var orderItem = _.find(window.orderBackbone[sku], { orderID: o.utils.getSelectedOrderId(), storeID: storeId });
        return orderItem ? orderItem.lineItem : -1;
    }

    /**
     * Gets the particular order item index associated with a sku and a storeId.
     * window.orderBackbone[sku][{storeId0}...{storeIdN}]
     * @param {string} sku
     * @param {string} storeId
     * @returns {int} array index or -1 = not found
     */
    function getOrderItemIndex(sku, storeId) {
        var selectedStore = storeId;
        var selectedOrder = utils.getSelectedOrderId();

        selectedStore = convertStoreIdToString(selectedStore);
        return _.findIndex(window.orderBackbone[sku], function (ol) {
            return ol.orderID === selectedOrder && ol.storeID === selectedStore;
        });
    }

    /**
     * Get line Item index relative to sku window.orderBackbone[sku][index]
     * @param {string} sku
     * @param {string} storeId
     * @param {number} lineItem
     * @returns {number} array index or -1 = not found
     */
    function getOrderLineItemIndex(sku, storeId, lineItem) {
        var selectedOrder = utils.getSelectedOrderId();
        return _.findIndex(window.orderBackbone[sku], function (ol) {
            return ol.orderID === selectedOrder && ol.storeID === storeId && ol.lineItem === lineItem;
        });
    }

    function getOrderBackbone(skus, orgId, done) {
        if (_.isString(orgId) && _.size(orgId) > 0 && _.isArray(skus) && _.size(skus) > 0) {
            var postParams = JSON.stringify(skus);
            $.ajax({
                type: "POST",
                data: postParams,
                cache: false,
                url: "api/orders/" + orgId + "/products/",
                contentType: "application/json",
                success: function (data) {
                    window.orderBackbone = data;
                },
                complete: done
            });
            return true;
        } else {
            return false;
        }
    }

    /**
     * Update the order totals summary bar (if it exists)
     */
    function getOrderTotals() {
        updateOrderSortRefine();
        updateOrderTotals();
    }

    function updateOrderTotals() {
        var unitQty = 0;
        var orderTotal = 0;

        if (!window.sortrefine) return;

        var items = window.sortrefine.filter(function (item) {
            return item.Order === 1 && item.filteredOut === 0;
        });

        var selectedOrderId = utils.getSelectedOrderId();
        var storeIds = utils.getSelectedStoreIds();

        items.forEach(function (item) {
            if (!(item.item in window.orderBackbone)) return;

            window.orderBackbone[item.item].forEach(function (lineItem) {
                var itemPrice = parseFloat(window.prices[item.item]);
                var isItemInOrder = isLineItemInOrder(lineItem, selectedOrderId, storeIds);

                if (isItemInOrder && !_.isNaN(itemPrice)) {
                    unitQty += _.parseInt(lineItem.units, 10);
                    orderTotal += itemPrice * _.parseInt(lineItem.units, 10);
                }
            });
        });

        updateOrderBar(unitQty, orderTotal, items.length);
    }

    /**
     * Get line items that have a valid qty, valid orderId (currently selected, 0, or -1), and for a currently selected store.
     * @param {string} sku
     * @returns {Array<Object>}
     */
    function getValidLineItems(sku, storeId) {
        var selectedStores = _.isNil(storeId) ? utils.getSelectedStoreIds() : [storeId];
        var selectedOrder = utils.getSelectedOrderId();

        return _.filter(window.orderBackbone[sku], function (ol) {
            var validOrder = (selectedOrder === 0 || selectedOrder === -1 || selectedOrder === ol.orderID)
            var validLineItem = ol.lineItem !== null;
            var store = convertStoreIdToString(ol.storeID);

            return validOrder && validLineItem && _.indexOf(selectedStores, store) > -1;
        });
    };

    /**
     * Validates input for the numerical order inputs
     * ([0-9] > -1|""|IBIDIE_INVENTORY_NUMBER)
     * @param {string} units
     * @returns {boolean}
     */
    function isValidInput(units) {
        var unitsInt = _.parseInt(units, 10);
        return units === "" || (unitsInt > -1 || unitsInt === IBIDIE_INVENTORY_NUMBER);
    }

    /**
     * Validates that a line item is a number and is not NaN
     * @param {} units
     * @returns {}
     */
    function isValidLineItem(lineItem) {
        var lineItemNum = _.parseInt(lineItem, 10);
        return _.isNumber(lineItemNum) && !_.isNaN(lineItemNum);
    }

    /**
     * @param {string} sku
     * @param {number} index >= 0
     */
    function removeOrderObject(sku, index) {
        if (index > -1) {
            window.orderBackbone[sku].splice(index, 1);
            if (_.size(window.orderBackbone[sku]) === 0) {
                delete window.orderBackbone[sku];
            }
        }
    }

    function hasStatusAlert() {
        var orderId = o.utils.getSelectedOrderId();
        return utils.getUnitsViewType() === unitView.ORDER &&
               hasAcknowledgedStatusAlertForOrder(orderId) &&
               statusAlertExists(orderId);
    }

    function statusAlertExists(orderId) {
        var statuses = window.getListViewProperty("orderStatuses");
        var statusRegex = /submitted/i;
        return  statuses &&
                statuses[orderId] &&
                statuses[orderId][0] &&
                statuses[orderId][0].status &&
                statuses[orderId][0].status.match(statusRegex);
    }

    function hasAcknowledgedStatusAlertForOrder(orderId) {
        return (!window.hasSeenStatusAlert || (window.hasSeenStatusAlert && !window.hasSeenStatusAlert[orderId]));
    }

    function confirmSubmittedOrderEdit() {
        var orderId = o.utils.getSelectedOrderId();

        if (!window.hasSeenStatusAlert) {
            window.hasSeenStatusAlert = {};
        }

        window.hasSeenStatusAlert[orderId] = true;

        var message = "";
        if (isPublisher) {
            message = getRes("order_alert_summited_a") + "\n\n" +
                      getRes("order_alert_summited_b") + "\n\n";
        } else {
            message = getRes("order_alert_summited_rep_a") + "\n\n" +
                      getRes("order_alert_summited_rep_b") + "  " +
                      getRes("order_alert_summited_rep_c") + "\n\n";
        }
        message += getRes("order_alert_mark_complete");

        return confirm(message);
    }

    function updateRelatedProductQuantities(sku, storeId, units) {
        $('.title-original-quantity-' + sku + '-' + storeId).each(function () {
            $(this).val(units || 0);
        });
    }

    /**
     * Save input units for order element.
     * @param {DOM Object} elem
     * @param {string} sku
     * @param {Object} event
     */
    function saveUnits(elem, sku, event) {
        var $e = $(elem);

        if (isSaveUnitsInProgress($e)) {
            return;
        }

        if (event && event.type !== "blur") {
            return;
        }

        markSaveUnitsInProgress($e);
        var units = $e.val();
        var originalUnits = $e.attr("data-savedval");
        var storeId = $e.data("storeid");
        var savingError = false;

        var finalizeSaveUnits = function () {
            enableAddLineItemButtonsForSku(sku);
            cleanupInput(elem, units, originalUnits, savingError);
            markSaveUnitsComplete($e);
        };

        if (units === originalUnits) {
            finalizeSaveUnits();

            return;
        }

        storeId = convertStoreIdToString(storeId);
        var isValidChange = isValidInput(units);

        if (!isValidChange || (hasStatusAlert() && !confirmSubmittedOrderEdit())) {
            savingError = true;

            if (!isValidChange) {
                alert(translations['order_quantity_error']);
            }

            finalizeSaveUnits();

            return;
        }

        view.toggleSkuSaving(sku);

        var isLineItem = !_.isNil($e.data("lineitem"));
        var lineItem = isLineItem ? $e.data("lineitem") : getLineItemValue(sku, storeId);

        if (!isPublisher || utils.getUnitsViewType() === unitView.ORDER) {
            var saveSuccess = function (elem, lineItem) {
                skuSaveSuccess(elem, lineItem);
                updateOrder(lineItem.sku);
                finalizeSaveUnits();
                getRefineNumbers();

                var nextInputTarget = event && event.relatedTarget;

                if (nextInputTarget) {
                    if (typeof nextInputTarget.focus === 'function') {
                        nextInputTarget.focus();
                    }
                    if (typeof nextInputTarget.select === 'function') {
                        nextInputTarget.select();
                    }
                }
            }

            updateOrderBackbone(sku, storeId, units, isLineItem, lineItem);
            updateDatabaseUnits(sku, storeId, units, lineItem, elem, isLineItem, saveSuccess);
            updateRelatedProductQuantities(sku, storeId, units);
            updateOrderSuggestionFilters(sku, FILTER_TYPE_SUGGESTIONS);
        } else if (isPublisher) {
            if (event && window.isIeEleven) {
                event.preventDefault();
                event.stopPropagation();
            }

            var suggestion = {
                orgId: getSelectedAccountOrgId(),
                storeId: storeId,
                sku: sku,
                originalUnits: originalUnits,
                newUnits: units,
                units: units
            };

            updateRepSuggestions(suggestion).done(function () {
                populateSuggestionRefine(FILTER_TYPE_PUB_SUGGESTIONS);
                $(elem).val(units).attr("data-savedval", units);
                view.toggleSkuSaving(sku);
                view.updateSkuSuggestionTotals(sku);
                window.ePlus.modules.suggestions.getSuggestionTotals(suggestion);
                updateOrderSuggestionFilters(sku, FILTER_TYPE_PUB_ORDERS);

                var nextInputTarget = event && event.relatedTarget;

                if (nextInputTarget) {
                    nextInputTarget.focus();
                }

                finalizeSaveUnits();
                getRefineNumbers();
            }).fail(function () {
                alert(data.text);

                finalizeSaveUnits();
                getRefineNumbers();
            });
        } else {
                finalizeSaveUnits();
        }
    }

    var saveUnitsInProgressClass = "save-units-in-progress";

    function isSaveUnitsInProgress($e) {
        return $e.hasClass(saveUnitsInProgressClass);
    }

    function markSaveUnitsInProgress($e) {
        $e.addClass(saveUnitsInProgressClass);
    }

    function markSaveUnitsComplete($e) {
        $e.removeClass(saveUnitsInProgressClass);
    }

    function saveUnitsToStore(sku, units, originalUnits, storeId, callback) {
        if (units !== originalUnits) {
            storeId = convertStoreIdToString(storeId);
            var lineItem = getLineItemValue(sku, storeId);

            if (!isPublisher || utils.getUnitsViewType() === unitView.ORDER) {
                updateOrderBackbone(sku, storeId, units, false, lineItem);
                updateDatabaseUnits(sku, storeId, units, lineItem, null, false, callback);
                updateOrderSuggestionFilters(sku, FILTER_TYPE_SUGGESTIONS);
                updateOrder(sku);
            } else if (isPublisher) {
                var suggestion = {
                    orgId: getSelectedAccountOrgId(),
                    storeId: storeId,
                    sku: sku,
                    originalUnits: originalUnits,
                    newUnits: units,
                    units: units
                };

                updateRepSuggestions(suggestion).done(function () {
                    populateSuggestionRefine(FILTER_TYPE_PUB_SUGGESTIONS);
                    view.updateSkuSuggestionTotals(sku);
                    window.ePlus.modules.suggestions.getSuggestionTotals(suggestion);
                    updateOrderSuggestionFilters(sku, FILTER_TYPE_PUB_ORDERS);
                }).fail(function () {
                    alert(data.text);
                });
            }
        }
    }

    function updateRepSuggestions(suggestion) {
        return $.ajax({
            url: 'api/me/suggestions/',
            type: suggestion.units === '' ? 'DELETE' : 'POST',
            data: suggestion
        });
    }

    function buildSuggestions(orgId, storeIds, skus, units) {
        var suggestions = [];

        skus.forEach(function (sku) {
            storeIds.forEach(function (storeId) {
                var suggestion = {
                    orgId: orgId,
                    storeId: storeId,
                    sku: sku,
                    units: units || ''
                };

                suggestions.push(suggestion);
            })
        });

        return suggestions;
    }

    function deleteSuggestionsOnSelected() {
        var orgId = getSelectedAccountOrgId();
        var storeIds = getSelectedStoreIds();
        var skus = window.getSelectedItems();

        if (!skus.length) {
            modalAlert(translations['must_select_one_or_more']);
            return;
        }

        var suggestions = buildSuggestions(orgId, storeIds, skus);

        deleteRepSuggestions(suggestions)
            .done(function () {
                view.updateSuggestions(suggestions);
                modalAlert(translations['suggestions_removed']);
            })
            .fail(function () {
                modalAlert(translations['error_removing_suggestions']);
            });
    }

    function deleteRepSuggestions(suggestions) {
        return $.ajax({
            url: 'api/me/suggestions/batch',
            type: 'DELETE',
            contentType: 'application/json',
            data: JSON.stringify(suggestions)
        })
    }

    function isLineItemInOrder(lineItem, orderId, storeIds) {
        var units = parseInt(lineItem.units, 10);
        return (orderId === 0 || orderId === -1 || orderId === lineItem.orderID)
            && storeIds.some(function (storeId) { return lineItem.storeID === storeId })
            && units > 0;
    }

    /**
     * Sets/updates the window.sortrefine object for orders.
     */
    function updateOrderSortRefine() {
        if (!window.sortrefine || !window.orderBackbone) return;

        var currentOrderId = utils.getSelectedOrderId();
        var storeIds = utils.getSelectedStoreIds();

        var ORDER_QUANTITY = 'OrderQuantity';
        var ORDER = 'Order';
        window.sortrefine.forEach(function (item, i) {
            var isItemInOrder = item.item in window.orderBackbone
                && window.orderBackbone[item.item].some(function (lineItem) {
                    return isLineItemInOrder(lineItem, currentOrderId, storeIds);
                });

            if (isItemInOrder) {
                if (item.item in window.orderBackbone) {
                    var skuOrderedUnits = getSkuOrderUnits(item.item, currentOrderId, storeIds);
                    setSortRefineProperty(i, ORDER_QUANTITY, skuOrderedUnits);
                }
                setSortRefineProperty(i, ORDER, 1);
            } else {
                setSortRefineProperty(i, ORDER, 0);
            }
        });
    }

    function getSkuOrderUnits(sku, currentOrderId, storeIds) {
        var units = 0;

        if (!_.isNil(window.orderBackbone[sku])) {
            window.orderBackbone[sku].forEach(function (ol) {
                var itemPrice = parseFloat(window.prices[sku.item]);
                var doIncludeUnits = ol.units > 0 && _.indexOf(storeIds, ol.storeID) > -1 && (currentOrderId === ol.orderID || currentOrderId === 0 || currentOrderId === -1);
                if (doIncludeUnits) {
                    units += parseInt(ol.units, 10);
                }
            });
        }

        return units;
    }

    function showStoreCategories(storeId, sku) {
        if (utils.buildStoreCategorySelector(storeId, sku, 1).html() !== "") utils.buildStoreCategoryContainerSelector(storeId, sku, 1).show();
        if (utils.buildStoreCategorySelector(storeId, sku, 2).html() !== "") utils.buildStoreCategoryContainerSelector(storeId, sku, 2).show();
    }

    /**
     * Update categories for window.orderBackbone order object
     * @param {string} sku
     * @param {string} storeId
     * @param {number} lineItem
     * @param {string} category1
     * @param {string} category2
     */
    function updateCategories(sku, storeId, lineItem, category1, category2) {
        if (!isValidLineItem(lineItem)) {
            return;
        }
        lineItem = _.parseInt(lineItem, 10);
        var index = getOrderLineItemIndex(sku, storeId, lineItem);
        if ((!_.isNil(category1) || category1 === "") && index > -1) {
            window.orderBackbone[sku][index].category = category1;
        }
        if ((!_.isNil(category2) || category2 === "") && index > -1) {
            window.orderBackbone[sku][index].category2 = category2;
        }
    }

    /**
     * Saves change to database for retail users.
     * @param {string} sku
     * @param {string} storeId
     * @param {string} units
     * @param {number} lineItem
     * @param {DOM Object} elem
     * @param {function} done
     */
    function updateDatabaseUnits(sku, storeId, units, lineItem, elem, isLineItemUpdate, done, category, category2, isNew) {
        api.savingLock = true;
        var selectedOrder = o.utils.getSelectedOrderId();
        var price = window.prices[sku];
        var url = "/api/orders/" + selectedOrder + "/lineItems/";
        var type;
        if (_.isNil(lineItem) || lineItem === -1) {
            type = "POST";
        } else {
            url += lineItem;
            type = "PUT";
        }

        if (isNew) {
            url += "?isNew=" + isNew;
        }

        if (units !== "" || isLineItemUpdate) {
            var values = {
                orderId: selectedOrder,
                sku: sku,
                storeId: storeId,
                referenceId: lineItem,
                units: units,
                price: price
            };
            if (isLineItemUpdate) {
                values.category = category || utils.buildLineItemCategorySelector(storeId, sku, lineItem, 1).html() || "";
                values.category2 = category2 || utils.buildLineItemCategorySelector(storeId, sku, lineItem, 2).html() || "";
            }

            $.ajax({
                url: url,
                type: type,
                data: JSON.stringify(values),
                dataType: "json",
                contentType: "application/json; charset=utf-8"
            })
                .done(function (data) {
                    var backboneIndex = isLineItemUpdate ? getOrderLineItemIndex(sku, storeId, lineItem) : getOrderItemIndex(sku, storeId);
                    if (backboneIndex > -1 && !_.isNil(data)) {
                        window.orderBackbone[sku][backboneIndex].lineItem = !_.isNaN(_.parseInt(data.referenceId, 10)) ? _.parseInt(data.referenceId, 10) : -1;
                    }
                    api.savingLock = false;
                    if (_.isFunction(done)) {
                        done(elem, values);
                    }
                })
                .fail(function (data) {
                    if (window.LogRocket) {
                        window.LogRocket.captureMessage(data.statusText, {
                            tags: {
                                function: 'Orders'
                            },
                            extra: {
                                responseText: data && data.responseText
                            }
                        })
                    }

                    handleFailedLineItemSave(data);
                });
        } else {
            type = "DELETE";
            var valuesDelete = {
                orderId: selectedOrder,
                sku: sku,
                storeId: storeId,
                referenceId: lineItem
            };
            $.ajax({
                url: url,
                type: type,
                data: JSON.stringify(valuesDelete),
                dataType: "json",
                contentType: "application/json; charset=utf-8"
            })
                .done(function () {
                    var repDiscounts = window.ePlus.modules.orders.lineItems.repDiscounts;

                    repDiscounts.ui.updateListViewLineItemRepDiscount({
                        discount: null,
                        sku: sku,
                        storeId: storeId
                    });
                    repDiscounts.ui.updateListViewLineItemAverageRepDiscount(sku);
                    
                    api.savingLock = false;
                    if (_.isFunction(done)) {
                        done(elem, valuesDelete);
                    }
                })
                .fail(function(data) {
                    handleFailedLineItemSave(data);
                });
        }
    }

    function handleFailedLineItemSave(responseData) {
        if (responseData.status === 403) {
            alert(getRes("login_to_perform_action"));
            loginAndReturnToCurrentPage();
        } else if(responseData.status !== 0) {
            alert(getRes("error_may_have_occurred_refresh"));
        }
    }

    /**
     * Remove saving state from input field.
     * @param {DOM Object} elem
     */
    function skuSaveSuccess(elem, lineItem) {
        view.toggleSkuSaving(lineItem.sku);
    }

    /**
     * Update order UI elements related to SKU. Update order totals.
     * @param {string} sku
     */
    function updateOrder(sku) {
        if (utils.getUnitsViewType() === unitView.ORDER) {
            view.updateSku(sku);
            getOrderTotals();
        } else if (_.isFunction(window.refreshUnitSummaryAndRefinements)) {
            view.updateSkuSuggestionTotals(sku);
            window.refreshUnitSummaryAndRefinements();
        }
    }

    /**
     * Update window.orderBackbone to reflect changes in to order elements.
     * Assumes units are a valid input.
     * Saves/Deletes order line items in the database
     * @param {string} sku
     * @param {string} storeId
     * @param {integer} units
     */
    function updateOrderBackbone(sku, storeId, units, isLineItem, lineItem, category, category2) {
        var selectedOrder = utils.getSelectedOrderId();

        var backboneIndex = isLineItem ? getOrderLineItemIndex(sku, storeId, lineItem) : getOrderItemIndex(sku, storeId);
        if (backboneIndex > -1) {
            if (units === "" && !isLineItem) removeOrderObject(sku, backboneIndex);
            else {
                window.orderBackbone[sku][backboneIndex].units = _.isNaN(_.parseInt(units, 10)) ? "" : _.parseInt(units, 10);
                if (_.isString(category)) {
                    window.orderBackbone[sku][backboneIndex].category = category;
                }
                if (_.isString(category2)) {
                    window.orderBackbone[sku][backboneIndex].category2 = category2;
                }
            }
        } else if (units !== "" || isLineItem) {
            var element = {
                storeID: storeId,
                orderID: selectedOrder,
                units: _.isNaN(_.parseInt(units, 10)) ? "" : _.parseInt(units, 10),
                lineItem: lineItem
            };

            if (_.has(window.orderBackbone, sku)) {
                window.orderBackbone[sku].push(element);
            } else {
                window.orderBackbone[sku] = [element];
            }
        }
    }

    /**
     * Update UI elements to reflect order totals.
     * @param {number} unitQty
     * @param {number} orderTotal
     * @param {number} titleCount
     */
    function updateOrderBar(unitQty, orderTotal, titleCount) {
        $("#orderedTitles").text(titleCount);
        $("#orderCurrencyValue").val(orderTotal);
        formatCurrency(orderTotal, ePlus.user.culture, { currency: ePlus.user.currencyCode }, function (formattedOrderTotal) {
            $("#orderCurrency").text(formattedOrderTotal);
        });
        $("#orderQty").text(unitQty);
    }

    function createLineItemInput(storeId, sku) {
        var $this = buildAddLineItemButtonSelector(storeId, sku);

        if ($this.hasClass('disabled')) {
            return;
        }

        var storeLineItems = getLineItems(sku, storeId);
        utils.buildInputSelector(storeId, sku).prop("disabled", true).addClass("bgdDarkGray non-clickable");
        if (_.size(storeLineItems) === 1) {
            $(".catLocDiv_" + storeId + "_" + sku).hide();
            var $storeCatOne = utils.buildStoreCategorySelector(storeId, sku, 1);
            var $storeCatTwo = utils.buildStoreCategorySelector(storeId, sku, 2);
            var units = utils.buildInputSelector(storeId, sku).val();
            var lineItem = storeLineItems[0].lineItem;
            var category = $storeCatOne.html() || "";
            var category2 = $storeCatTwo.html() || "";
            utils.buildStoreCategoryContainerSelector(storeId, sku, 1).hide();
            utils.buildStoreCategoryContainerSelector(storeId, sku, 2).hide();
            updateOrderBackbone(sku, storeId, units, true, lineItem, category, category2);
            utils.buildLineItemStoreContainerSelector(storeId, sku).append(templateCache.getNewLineItemInput(sku, storeId, lineItem, units, translations['delete_store_line_item'], [category, category2], translations['edit_category'], translations['must_select_order']));
            enableLineItemPopover(sku, storeId, lineItem);
            updateDatabaseUnits(sku, storeId, units, lineItem, null, true, null, category, category2);
        }

        //Create backbone element with null lineItem
        updateOrderBackbone(sku, storeId, "", true, null);
        updateDatabaseUnits(sku, storeId, "", null, null, true, finalizeCreateLineItemInput, "", "", true);
    }

    function finalizeCreateLineItemInput(elem, values) {
        updateOrder(values.sku);
        enableLineItemPopover(values.sku, values.storeId, values.lineItem);
    }

    function deleteLineItemInput(storeId, sku, lineItem) {
        if (confirm(translations['confirm_line_item_delete'])) {
            var lineItemNumber = _.parseInt(lineItem, 10);
            if (_.isNaN(lineItemNumber)) {
                return;
            }
            removeOrderObject(sku, getOrderLineItemIndex(sku, storeId, lineItemNumber));
            utils.buildLineItemsContainerSelector(storeId, sku, lineItemNumber).remove();
            var storeLineItems = getLineItems(sku, storeId);
            if (_.size(storeLineItems) === 1) {
                var li = storeLineItems[0];
                var category = utils.buildLineItemCategorySelector(storeId, sku, li.lineItem, 1).html() || "";
                var category2 = utils.buildLineItemCategorySelector(storeId, sku, li.lineItem, 2).html() || "";
                utils.buildLineItemsContainerSelector(storeId, sku, li.lineItem).remove();
                $(".catLocDiv_" + storeId + "_" + sku).show();
                utils.buildStoreCategorySelector(storeId, sku, 1).html(category);
                utils.buildStoreCategorySelector(storeId, sku, 2).html(category2);
                showStoreCategories(storeId, sku);
            }
            updateOrder(sku);
            updateDatabaseUnits(sku, storeId, "", lineItemNumber, null, false);
        }
    }

    function convertStoreIdToString(storeId) {
        if (_.isNumber(storeId)) {
            return _.toString(storeId);
        }
        return storeId;
    }

    api = {
        view: view,
        utils: utils,
        checkOrder: checkOrder,
        checkKey: checkKey,
        createLineItemInput: createLineItemInput,
        deleteLineItemInput: deleteLineItemInput,
        getOrderTotals: getOrderTotals,
        updateOrderSortRefine: updateOrderSortRefine,
        updateOrderTotals: updateOrderTotals,
        getOrderBackbone: getOrderBackbone,
        getValidLineItems: getValidLineItems,
        init: init,
        saveUnits: saveUnits,
        saveUnitsToStore: saveUnitsToStore,
        deleteSuggestionsOnSelected: deleteSuggestionsOnSelected,
        /**
        * savingLock - Current saving state of orders module.
        */
        savingLock: false,
        setStoreMultiLineMode: setStoreMultiLineMode,
        updateCategories: updateCategories,
        disableAddLineItemButtonsForSku: disableAddLineItemButtonsForSku
    }

    return api;
})();;
//TODO: Basic documentation about plugin
(function ($, undefined) {
    $.widget("eplus.resequenceList", {
        version: "0.1",

        // Default options
        options: {
            listTitle: "Item Resequence",
            data: [],
            fieldsToShow: [],
            fieldLabels: [],
            doAutoSave: false,
            dataImageIndex: function () { },
            dataDateIndex: function () { },
            template: "basicItemView",
            resequenceIcon: "icon-move-vertical",
            imagePropertyName: "imageUri",
            imageHeight: 140,
            doFormatDates: true,
            rowHeight: 90,
            saveUrl: "",
            onSave: function () { },
            saveButtonId: ""
        },

        _create: function () {
            var t = this;

            if (t.options.data[0] == null) {
                console
                    .error("ep.standardResequenceList: Please provide a valid JSON array for list creation. Options: data");
            }

            //TODO: More state checking before init
        },

        _init: function () {
            var t = this;
            t.element.addClass("resequenceListArea");
            var $ul = $("<ul></ul>");
            t.element.append($ul);
            t._initDataView();

            $ul.sortable({
                handle: ".resequenceDragHandle",
                update: t._sortableUpdate.bind(t)
            });

            t._initEvents();
        },

        _remove: function () {
            $(this).empty();
        },

        _destroy: function () {
            $.Widget.prototype.destroy.call(this);
        },

        _initEvents: function () {
            var self = this;
            $("#" + this.options.saveButtonId).on("click", function () {
                self._saveData();
            });
        },

        _initDataView: function () {
            switch (this.options.template) {
                case "basicItemView":
                    this._initBasicItemView();
                    break;
                case "basicItemViewWithImage":
                    this._initBasicItemView(true);
                    break;
                default:
                    this._initBasicItemView();
            }
        },
        _initBasicItemView: function (doShowImage) {
            var $targetUl = this.element.children("ul");
            for (var i = 0; i < this.options.data.length; i++) {
                $targetUl.append(this._buildListItem(i, this.options.data[i], doShowImage));
            }
        },
        _buildListItem: function (index, dataObject, doShowImage) {
            var li = "<li data-originalOrder='" + index + "'>";
            li += this._getSortIconMarkup();
            if (doShowImage) {
                li += this._getImageMarkup(dataObject);
            }
            li += this._getContentMarkup(dataObject) + "</li>";

            return li;
        },
        _getSortIconMarkup: function () {
            return "<div class='resequenceIconContainer listComponent resequenceDragHandle center'><span class='" + this.options.resequenceIcon + " resequenceIcon'></span></div>";
        },
        _getImageMarkup: function (item) {
            if (this.options.fieldsToShow.indexOf(this.options.imagePropertyName) >= 0 &&
                item[this.options.imagePropertyName] != null) {
                return "<div class='imageContainer listComponent center'>" +
                    this._renderAsImage(item[this.options.imagePropertyName]) +
                    "</div>";
            } else {
                console
                    .warn("ep.standardResequenceList: Image property name is not found in one or more of input items. Options: imagePropertyName");
                return "<div class='listComponent center'></div>";
            }
        },
        _getContentMarkup: function (item) {
            var content = "<div class='listComponent contentContainer'>";
            for (var i = 0; i < this.options.fieldsToShow.length; i++) {
                if (item[this.options.fieldsToShow[i]] != null &&
                    this.options.fieldsToShow[i] !== this.options.imagePropertyName) {
                    content += i === 0 ? "<span class='contentHead'>" : "<span class='contentBody'>";

                    content += this._getLabel(i) + item[this.options.fieldsToShow[i]];

                    content += "</span>";
                    content += i !== this.options.fieldsToShow.length - 1 ? "<br/>" : "";
                }
            }

            content += "</div>";
            return content;
        },
        _getLabel: function (index) {
            return this.options.fieldLabels[index] != null ? this.options.fieldLabels[index] : "";
        },
        _renderAsImage: function (imageUri) {
            var imageUrl = encodeURI(imageUri + "?height=" + this.options.imageHeight + "&scale=both");
            return "<img src='" + imageUrl + "'/>";
        },
        _sortableUpdate: function (event, ui) {
            if (this.options.doAutoSave) {
                this._saveData();
            }
        },
        _saveData: function () {
            var t = this;

            var newSequence = [];
            var items = $(".resequenceListArea li");

            items.each(function () {
                newSequence.push($(this).attr("data-originalOrder"));
            });

            var newOrderDataObjects = [];
            $.each(newSequence, function (index, value) {
                newOrderDataObjects.push(t.options.data[value]);
            });

            var postParams = JSON.stringify(newOrderDataObjects);
            $.ajax({
                type: "POST",
                data: postParams,
                url: this.options.saveUrl,
                contentType: "application/json",
                success: this.options.onSave
            });
        }
    });
})(jQuery);
;
/**
 * Stores HTML generators or pre-compiled Lodash templates.
 * Interpolate: {{var1}}
 * Evaluate: <% javascript code %>
 * Escape: {{: escapedVar1 }}
 */
var templateCache = (function () {
    /**
     * @property dismissableInfoAlert Template for a dismissable info alert container. By default, alert is hidden. Arguments: id, message
     * @property dismissableWarningAlert Template for a dismissable warning alert container. By default, alert is hidden. Arguments: id, message
     * @property getFilterSelectTemplate Template for listview refinement for an individual filter type. 
     * @property getFilterSelectTemplate Template for listview refinement expand for an individual filter type.
     * @property loadingAnimation Template for svg animation loader. Arguments: svgLoaderClass
     */
    var api = {
        init: init,
        dismissableInfoAlert: function () { },
        dismissableWarningAlert: function () { },
        getFilterSelectTemplate: function() { },
        getFilterSelectExpandTemplate: function () { },
        getNewLineItemInput: getNewLineItemInput,
        loadingDisplay: function () {},
        loadingAnimation: function() {}
    }
    function init(hiddenText) {
        if (_) {
            _.templateSettings.interpolate = /{{([\s\S]+?)}}/g; //{{ var1}}
            _.templateSettings.escape = /{{:([\s\S]+?)}}/g; //{{: var1}}
            api.hiddenCount = _.template("({{hiddenCount}} " + hiddenText + ")");
            api.dismissableInfoAlert = _.template(
                "<div id=\"{{id}}\" style=\"display:none\" class=\"new-look-alert new-look-alert-info new-look-alert-dismissible textMedium\" role=\"alert\">{{message}}" +
                "<a href=\"#\" class=\"close alert-link\" aria-label=\"close\">×</a></div>"
            );
            api.dismissableWarningAlert = _.template(
                '<div id="{{id}}" style="display:none" class="new-look-alert new-look-alert-warning new-look-alert-dismissible textMedium" role="alert">{{message}}' +
                '<a href="#" class="close alert-link" aria-label="close">×</a></div>'
            );
            api.loadingDisplay = _.template(
                '<div style="display: flex;">' + 
                    '<div class="sequence" style="margin: auto 10px auto auto;">' +
                        '<div>' +
                            '<svg height="16" width="42" xmlns="http://www.w3.org/2000/svg">' +
                                '<circle class="seq-preload-circle seq-preload-circle-1 {{svgLoaderClass}}" cx="4" cy="8" r="4"></circle>' +
                                '<circle class="seq-preload-circle seq-preload-circle-2 {{svgLoaderClass}}" cx="17" cy="8" r="6"></circle>' +
                                '<circle class="seq-preload-circle seq-preload-circle-3 {{svgLoaderClass}}" cx="34" cy="8" r="8"></circle>' +
                            '</svg>' +
                        '</div>' +
                    '</div>' +
                    '<div class="bold" style="margin: auto auto auto 0; height: 20px;">{{loadingText}}</div>' +
                '</div>');
            api.loadingAnimation = _.template(
                    '<div class="sequence" style="margin: 0;">' +
                        '<div>' +
                            '<svg height="16" width="42" xmlns="http://www.w3.org/2000/svg">' +
                                '<circle class="seq-preload-circle seq-preload-circle-1 {{svgLoaderClass}}" cx="4" cy="8" r="4"></circle>' +
                                '<circle class="seq-preload-circle seq-preload-circle-2 {{svgLoaderClass}}" cx="17" cy="8" r="6"></circle>' +
                                '<circle class="seq-preload-circle seq-preload-circle-3 {{svgLoaderClass}}" cx="34" cy="8" r="8"></circle>' +
                            '</svg>' +
                        '</div>' +
                    '</div>');
            api.getFilterSelectTemplate = _.template(getFilterSelectTemplateText());
            api.getFilterSelectExpandTemplate = _.template(getFilterSelectTemplateExpandText());
        }
    }

    /**
     * Returns HTML string for new line item input element
     * @param {string} sku
     * @param {string} storeId
     * @param {number} lineItem
     * @param {number} units
     * @param {string} removeLineItemText
     * @param {Array[string]} categories
     * @param {string} editCategoryTextrem
     * @param {string} mustSelectOrderText
     */
    function getNewLineItemInput(sku, storeId, lineItem, units, removeLineItemText, categories, editCategoryText, mustSelectOrderText) {
        if (_.isNil(categories)) categories = ["", ""];
        if (_.isNil(removeLineItemText)) removeLineItemText = "";
        if (_.isNil(editCategoryText)) editCategoryText = "";
        if (_.isNil(mustSelectOrderText)) mustSelectOrderText = "";
        var html = "" +
        "<div id='lineItems_" + sku + "_" + storeId + "_" + lineItem + "' class='lineItems_" + sku + "_" + storeId + "_" + lineItem + " lineItemsContainer' style='background-color: #f4f4f4; '>" +
            "<div class='lineItem7' style='padding-top: 4px;'>" +
                "<span class='removeStoreLineItem_" + storeId + "_" + sku + " icon-delete-icon dropDown clickable' title='" + removeLineItemText + "' onclick='o.deleteLineItemInput(\"" + storeId + "\",\"" + sku + "\"," + lineItem + ")'></span>" +
            "</div>" +
            "<div class='storeCats lineItem53' style='width: 53% !important'>";
        _.forEach(categories, function (category, index) {
            if (_.isNil(category)) category = "";
            index++;
            html += "<div id='catLocDisplay_" + storeId + "_" + sku + "_" + lineItem + "_" + index + "' class='catLocDisplay_" + storeId + "_" + sku + "_" + lineItem + "_" + index + "'>" +
                        "<div id='catLoc_" + storeId + "_" + sku + "_" + lineItem + "_" + index + "' class='storeCats catLoc_" + storeId + "_" + sku + "_" + lineItem + "_" + index + " catLoc_" + sku + "' title='" + category + "'>" + category + "</div>" +
                    "</div>";
        });
            html += "</div>" +
            "<div id='catLocDiv_" + storeId + "_" + sku + "_" + lineItem + "' data-sku='" + sku + "' data-storeID='" + storeId + "' data-lineItem='" + lineItem + "' class='lineItem7 catLocDiv_" + storeId + "_" + sku + "_" + lineItem + " catPin icon-map-pin iconSVG_Darker catLocDiv' style='font-size: 14px;' title='" + editCategoryText + "'></div>" +
            "<div class='lineItem25'>" +
                "<input id='inputOrders_" + storeId + "_" + sku + "_" + lineItem + "' type='number' autocomplete='off'" +
                    "data-storeid='" + storeId + "' data-savedval='" + units + "' data-lineItem='" + lineItem + "' value='" + units + "'" +
                    "class='inputOrders_" + storeId + "_" + sku + "_" + lineItem + " inputOrder_" + sku + " font stripNumber bgdColorMyActions orderInput multiOrderBox'" +
                    "min='0'" +
                    "onfocus='javascript:o.checkOrder(\"" + mustSelectOrderText + "\");o.disableAddLineItemButtonsForSku(\"" + sku + "\");'" +
                    "onkeyup='javascript:o.checkKey(this, event, " + lineItem + ");'" +
                    "onblur='javascript:o.saveUnits(this, \"" + sku + "\", event, " + lineItem + ");'" +
                    "onwheel='javascript:this.blur();'" +
                    "/>" +
            "</div>" +
        "</div>";

        return html;
    }

    function getFilterSelectTemplateText() {
        var templateText = '<div class="{{ filterType }}_cont filter-container">' +
                                '<% _.forEach(filterOptionRows, function(rowIndex) { %>' +
                                    getFilterSelectRowTemplateText() +
                                    '<% if (doShowMore && rowIndex === 4) { %>' +
                                        '<div class="showMore_{{ filterType }}" style="display: none;"></div>' +
                                    '<% } %>' +
                                '<% }); %>' +
                                '<% if (doShowMore) { %>' +
                                    '<div class="toggle-more-less-refine lighterShade showRefineLink_{{ filterType }}" onclick="javascript:showMoreRefinements({{ filterType }});">' +
                                        '<div class="column icon-add-icon iconSVG toggle-more-less-refine-icon"></div>' +
                                        '<div class="expandLink">{{ showMoreText }}</div>' +
                                        '<div style="clear: both;"></div>' +
                                    '</div>' +
                                '<% } %>' +
                                '<div style="margin-top:10px;"><hr class="hrStandard"/></div>' +
                           '</div>';
        return templateText;
    }

    function getFilterSelectTemplateExpandText() {
        var templateText =
            '<% _.forEach(filterOptionRows, function(rowIndex) { %>' +
                getFilterSelectRowTemplateText() +
            '<% }); %>' +
            '<div style="display: none;" class="toggle-more-less-refine lighterShade hideRefineLink_{{ filterType }}" onclick="javascript:hideMoreRefinements({{ filterType }});">' +
                '<div class="column icon-remove-icon iconSVG toggle-more-less-refine-icon"></div>' +
                '<div class="expandLink">{{ showLessText }}</div>' +
                '<div style="clear: both;"></div>' +
            '</div>';

        return templateText;
    }

    function getFilterSelectRowTemplateText() {
        var templateRowText = 
        '<div id="{{ filterType }}_{{ rowIndex }}" data-attr="" data-row="{{ rowIndex }}" class="filterOption filterRow_{{ filterType }}" <% if (rowIndex > 4 && doShowMore) { %>style="display:none;" <% } %>>' +
            '<div id="f_{{ filterType }}_{{ rowIndex }}" data-section="{{ filterType }}_{{ rowIndex }}" class="<% if (doCheckBox) { %>box_checked<% } else {%>box_unchecked<%}%> filter filter_{{ filterType }}">&nbsp;</div>' +
            '<% if (isCategoryFilter) { %>' +
                '<div id="sDrop{{ filterType }}_{{ rowIndex }}" class="column icon-drop-down-icon iconSVG selectArrow refine-sub-list-icon" style="display:none;" onclick="javascript:toggleSubList({{ filterType }}, {{ rowIndex }});"></div>' +
            '<% } %>' +
            '<div id="fD_{{ filterType }}_{{ rowIndex }}" class="filterElement" style="max-width: {{elementWidth}}px;"></div>' +
            '<div class="refCount textSmall" id="fCount_{{ filterType }}_{{ rowIndex }}"></div>' +
            '<div style="clear: both;"></div>' +
        '</div>';

        return templateRowText;
    }

    return api;
})();;
ePlus.modules.addEditUserPhone = (function () {
    var loadPhoneEditControl = function () {
        var url = '/GetTreelineControl.aspx?controlName=/uc/address/AddEditUserPhone.ascx';
        $("#profile-phone").load(url);
    }

    var savePhoneNumber = function (phoneNumber) {
        if (!phoneNumber) {
            return;
        }
        $.ajax({
            url: '/api/v1/me/phone/' + encodeURIComponent(phoneNumber),
            type: 'POST'
        }).done(function() {
            loadPhoneEditControl();
            $("#missing-phone-number").hide();
            $("#phone-display-value").text(phoneNumber);
            $("#phone-request-display").show();

        });
    }

    var deletePhoneNumber = function () {
        $.ajax({
            url: '/api/v1/me/phone/',
            type: 'DELETE'
        }).done(function () {
            loadPhoneEditControl();
            $("#missing-phone-number").show();
            $("#phone-request-display").hide();
            $("#phone-display-value").text('');
        });
    }

    return {
        savePhoneNumber: savePhoneNumber,
        deletePhoneNumber: deletePhoneNumber,
        loadPhoneEditControl: loadPhoneEditControl
    }
})();;
window.ePlus.modules.viewReviewCopyRequests = (function () {
    var lineItems;

    var buildLineItemsFromDataTable = function (lineItemStatus, approvedQty) {
        lineItems = [];
        var rcRequests = $("#rcRequests").DataTable().rows('.selected').data();

        _.forEach(rcRequests, function (item, i) {
            var lineItem = item.Item3;
            lineItem.Status = lineItemStatus;
            lineItem.ApprovedQty = approvedQty;
            lineItems.push(lineItem);
        });
    }

    var buildLineItemsFromListView = function (listViewLineItems, lineItemStatus, approvedQty) {
        lineItems = [];

        _.forEach(listViewLineItems, function (item, i) {
            var lineItem = item;
            lineItem.Status = lineItemStatus;
            lineItem.ApprovedQty = approvedQty;
            lineItems.push(lineItem);
        });
    }

    var createUserRequestOrder = function (orderType, status, orgId, userId) {
        var today = new Date();

        var userRequestOrder = {
            userRequestOrderId: 0,
            name: today.toISOString(),
            type: orderType,
            status: status,
            auditOrgId: orgId,
            auditUserId: userId
        };

        var onSuccess = function (newUserRequestOrder) {
            _.forEach(lineItems, function (item, i) {
                item.UserRequestOrderId = newUserRequestOrder.userRequestOrderId;
            });

            window.ePlus.modules.prc.savePrcRequest("PATCH", null, lineItems,
                function () {
                    reloadList();
                    closeChildModal();
                    closeModal();
                });
        };

        var onError = function () { };
        ePlus.modules.prc.saveUserRequestOrder("POST", userRequestOrder, onSuccess, onError);
    }

    return {
        createUserRequestOrder: createUserRequestOrder,
        buildLineItemsFromDataTable: buildLineItemsFromDataTable,
        buildLineItemsFromListView: buildLineItemsFromListView
    }
})();;
(function (cc, $, _, undefined) {
    var createConstantContact = function (constantContactDTO) {
        var apiUrl = (ePlusUri + "api/v1/constantContact");
        var json = JSON.stringify(constantContactDTO);

        return $.ajax({
            url: apiUrl,
            data: json,
            type: "POST",
            contentType: "application/json; charset=utf-8",
            dataType: "json",
            crossDomain: true,
            xhrFields: {
                withCredentials: true
            }
        });
    }

    var defaults = {
        ePlusUri: 'https://edelweiss.plus'
    }

    function setDefaults(config) {
        defaults = $.extend({}, defaults, config);
        ePlusUri = config.ePlusUri;
    }

    cc.createConstantContact = createConstantContact;
    cc.setDefaults = setDefaults;
})(window.ePlus.modules.constantContact = window.ePlus.modules.constantContact || {}, jQuery, _);;
;
window.ePlus.modules.manageUserAffiliation = (function () {
    var res = window.ePlus.resources;
    var util = window.ePlus.util;

    var $form;
    var $formButton;

    var userAffiliationRequirements = {};
    var unsavedChanges = false;

    var unsavedChangesChild = { "title": false };

    var addRequirements = function (settings) {
        var requirements = window.ePlus.modules.manageUserAffiliation.requirements.getRequirements();

        requirements = _.filter(requirements, function (req) {
            return req.type === settings.type;
        });

        var $tbody = $('tbody', $('#' + settings.tableId));

        var tbodyHtml = '';
        requirements.forEach(function (r) {
            tbodyHtml += '<tr><td class="pad-left-10 pad-right-10">' + r.label + '</td></tr>';
        });
        $tbody.html(tbodyHtml);
        userAffiliationRequirements[settings.type] = requirements;

        var count = requirements ? requirements.length : 0;
        $('#' + settings.countId).html(count);
    };

    var closeModalAndReloadPage = function () {
        window.closeModal();
        window.reloadCurrentPage();
    }

    var doShowUnsavedChangesWarning = function () {
        return unsavedChanges;
    };

    var doShowUnsavedChangesChildWarning = function (child) {
        return unsavedChangesChild[child];
    };

    var formSubmitCleanup = function () {
        changeFormButtonDisabledStatus(false);
    };

    var getAffiliation = function () {
        var affiliation = $form.serializeJSON({
            parseNumbers: true,
            parseBooleans: true
        });

        affiliation.ValidationRules = getAffiliationRequirements();

        return affiliation;
    };

    function getAffiliationRequirements () {
        var requirements = getRequirements();
        var aggregatedRequirements = [];
        for (var prop in requirements) {
            if (requirements.hasOwnProperty(prop)) {
                requirements[prop].forEach(function (r) {
                    aggregatedRequirements.push({ validationType: r.type, value: r.value });
                });
            }
        }

        return aggregatedRequirements;
    }

    var getRequirementsByType = function (type) {
        return userAffiliationRequirements[type];
    };

    var initCreateForm = function () {
        initForm(function () {
            if (!isValidAffiliation()) return;
            var affiliation = getAffiliation();
            window.ePlus.modules.userAffiliation.createAffiliation(affiliation, function (affiliation) {
                unsavedChanges = false;
                ePlus.modules.manageUserAffiliation.filter.affiliationFilterCallback(affiliation.id, closeModalAndReloadPage);
            }, formSubmitCleanup);
        });
    };

    var initEditForm = function () {
        initForm(function () {
            if (!isValidAffiliation()) return;
            var affiliation = getAffiliation();
            window.ePlus.modules.userAffiliation.updateAffiliation(affiliation, function (affiliation) {
                unsavedChanges = false;
                ePlus.modules.manageUserAffiliation.filter.affiliationFilterCallback(affiliation.id, closeModalAndReloadPage);
            }, formSubmitCleanup);
        });
    };

    var initManageUserAffiliationForm = function (formData, groupBrowseViewDefinition) {
        $('#account-count').on('click', function () {
            window.ePlus.modules.manageUserAffiliation.requirements.openAutoCompleteModal(formData.affiliationId, formData.organizationAutoComplete, util.getEnumValue('groupValidation', 'ORGANIZATION'));
        });

        $('#affiliation-count').on('click', function () {
            window.ePlus.modules.manageUserAffiliation.requirements.openAutoCompleteModal(formData.affiliationId, formData.affiliationAutoComplete, util.getEnumValue('groupValidation', 'GROUP'));
        });

        $('#email-count').on('click', function () {
            var requirements = getRequirementsByType(util.getEnumValue('groupValidation', 'EMAIL'));
            var emails;
            if (requirements) {
                emails = requirements.map(function (i) { return i.value; });
            } else {
                emails = formData.emails;
            }

            window.ePlus.modules.manageUserAffiliation.requirements.openEmailModal(formData.affiliationId, emails, util.getEnumValue('groupValidation', 'EMAIL'));
        });

        ePlus.modules.manageUserAffiliation.filter.setInitialFilterDefinition(groupBrowseViewDefinition);
        ePlus.modules.manageUserAffiliation.filter.setIsNewFilterDefinition(_.isEmpty(groupBrowseViewDefinition));

        $('#title-filter-count').off().on('click', function () {
            ePlus.modules.manageUserAffiliation.filter.openFilterModal(formData.affiliationId);
        });

        $('#account-auto-complete-label').webuiPopover({
            cache: false,
            placement: 'auto',
            content: function () {
                return $('#account-requirements-table').html();
            },
            trigger: 'hover'
        });

        $('#affiliation-auto-complete-label').webuiPopover({
            cache: false,
            placement: 'auto',
            content: function () {
                return $('#affiliation-requirements-table').html();
            },
            trigger: 'hover'
        });

        $('#email-label').webuiPopover({
            cache: false,
            placement: 'auto',
            content: function () {
                return $('#email-requirements-table').html();
            },
            trigger: 'hover'
        });
    };

    var initTabs = function () {
        $('.tab', $('#affiliation-management-tabs')).on('click', function () {
            var dataTab = $(this).data('tab');
            $(this)
                .addClass('tab-selected')
                .siblings('.tab-selected')
                .removeClass('tab-selected');


            $(dataTab)
                .removeClass('hidden')
                .siblings('div.tab-content')
                .addClass('hidden');
        });
    };

    var setRequirements = function (req) {
        userAffiliationRequirements = req;
    };

    var setUnsavedChanges = function (status) {
        unsavedChanges = status;
    };

    var setUnsavedChangesChild = function (child, status) {
        unsavedChangesChild[child] = status;
    };

    var messageAffiliationMembers = function (affiliationId, message, callback) {
        $.ajax({
            type: 'PUT',
            url: 'api/affiliations/' + affiliationId + '/message',
            data: { '': message }
        }).done(function (ret) {
            if (typeof callback === 'function') {
                callback(ret);
            }
        }).fail(function (ret) {
            alert(getRes('enter_a_message'));
        });
    }

    function getRequirements() {
        return userAffiliationRequirements;
    }

    var initForm = function (onSubmit) {
        $form = $('#create-edit-user-affiliation');
        $formButton = $('#create-user-affiliation-btn');
        changeFormButtonDisabledStatus(false);

        $form.on('submit', function (e) {
            e.preventDefault();
            e.stopPropagation();

            changeFormButtonDisabledStatus(true);

            if (typeof onSubmit === 'function') {
                onSubmit();
            }
            
            return false;
        });
    };

    var isValidAffiliation = function() {
        if (!$form[0].checkValidity()) {
            $form.addClass('was-validated');
            return false;
        }

        return true;
    };

    function changeFormButtonDisabledStatus(doDisable) {
        $formButton.prop('disabled', doDisable);
    }

    return {
        addRequirements: addRequirements,
        doShowUnsavedChangesWarning: doShowUnsavedChangesWarning,
        doShowUnsavedChangesChildWarning: doShowUnsavedChangesChildWarning,
        getAffiliation: getAffiliation,
        getRequirementsByType: getRequirementsByType,
        initCreateForm: initCreateForm,
        initEditForm: initEditForm,
        initManageUserAffiliationForm: initManageUserAffiliationForm,
        initTabs: initTabs,
        setRequirements: setRequirements,
        setUnsavedChanges: setUnsavedChanges,
        setUnsavedChangesChild: setUnsavedChangesChild,
        messageAffiliationMembers: messageAffiliationMembers
    }
})();;
(function (fd, $, _) {
    var createFreshDeskTicket = function () {

        var saveAlert = "";

        var apiUrl = ("api/v1/freshDesk/email");

        if ($("#emailSubject").val() === "") {
            saveAlert += "Email Subject is required \n";
        }
        if ($("#contactName").val() === "") {
            saveAlert += "Contact is required \n";
        }
        if ($("#emailAddress").val() === "") {
            saveAlert += "Email Address is required \n";
        } else {
            if (validateEmail($("#emailAddress").val())) {
                saveAlert += "More than one email address was entered into the Email Address field. \n";
            }
        }

        if (CKEDITOR.instances.editor.getData() === "") {
            saveAlert += "Message is required";
        }
            
        if (saveAlert !== "") {
            alert(saveAlert);
        } else {
            var defaultCcEmail = "ccDefault@abovethetreeline.com";
            var enteredCcEmail = $("#carbonCopy").val();
            var freshDeskTicketDto = {
                email: $("#emailAddress").val(),
                name: $("#contactName").val(),
                subject: $("#emailSubject").val(),
                description: CKEDITOR.instances.editor.getData(),
                ccEmails: enteredCcEmail === "" ? defaultCcEmail : enteredCcEmail,
                fromEmailAddress: $("#fromAddress option:selected").text(),
                saveDefaultFromEmailAddress: $("#saveDefaultEmailAddress").is(':checked')
            };

            var json = JSON.stringify(freshDeskTicketDto);
            $.ajax({
                url: apiUrl,
                data: json,
                type: "POST",
                contentType: "application/json; charset=utf-8",
                dataType: "json",
                crossDomain: true,
                xhrFields: {
                    withCredentials: true
                },
                success: function () {
                    window.processCompletedAction();
                    console.log("The ticket has been created");
                },
                error: function (error) {
                    console.log(error);
                }
            });
        }
    };

    var defaults = {
        ePlusUri: "https://edelweiss.plus"
    };

    function setDefaults(config) {
        defaults = $.extend({}, defaults, config);
        window.ePlusUri = config.ePlusUri;
    };

    var getDefaultEmailAddress = function (callback) {
        var url = "api/v1/freshDesk/email/default";
        $.ajax({
            type: "GET",
            url: url,
            cache: false,
            contentType: "application/json"
        }).done(callback);
    }

    function validateEmail(email) {
        var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
        return !re.test(email);
    }

    var getFreshDeskTicketsforUser = function (emailAddress, menuItem) {
        var url = "api/v1/freshDesk/users/" + emailAddress + "/tickets";
        displayFreshDeskTickets(menuItem, url);
    }

    var getFreshDeskTicketsforAccount = function (orgId, menuItem) {
        var url = "api/v1/freshDesk/accounts/" + orgId + "/tickets";
        displayFreshDeskTicketsAccount(menuItem, url);
    }

    var displayFreshDeskTickets = function (menuItem, url) {
        var $this = $("#user-modal-" + menuItem + "-count");
        $.ajax({
            type: "GET",
            url: url,
            cache: false,
            contentType: "application/json"
        }).done(function (data) {
            var userModalMenuItemCount = $("#user-modal-" + menuItem + "-count");
            if (data === "") {
                $this.html(0);
                $this.show();
                $("#section_" + menuItem).html('<br/>No recent Freshdesk tickets');
            } else {
                $this.html(data.length);
                $this.show();

                var dHtml = "";
                for (var i = 0; i < data.length; i++) {
                    dHtml +=
                        "<div style='clear: both; border-bottom: 2px solid #545454;background-color: #ffffff; margin-top: 5px; margin-bottom: 5px;padding: 5px;'>";
                    dHtml +=
                        "<div><b>Ticket ID# <a style='text- decoration: none; font-weight: bold;' href='javascript: window.ePlus.modules.support.openFreshdeskTicket(" +
                        data[i].id +
                        ");'>" +
                        data[i].id +
                        "</a></b>&nbsp;&bull;&nbsp;" +
                        data[i].createdAt +
                        "&nbsp;&bull;&nbsp;" +
                        window.ePlus.modules.support.getFreshdeskStatus(data[i].status) +
                        "</div>";
                    dHtml += "<div><b>Subject:</b> " + data[i].subject + "</div>";
                    dHtml += "<div style='max-height: 150px; overflow: auto;'>" +
                        data[i].description +
                        "</div>";
                    dHtml += "</div>";
                }
                var buttonElement;
                var createButtonHtml = '<button id="createUserBtn1">New Ticket</button><br/>';
                if (dHtml !== "") {
                    $("#section_" + menuItem).html(createButtonHtml + dHtml);
                    buttonElement = document.getElementById("createUserBtn1");
                    if (buttonElement) {
                        buttonElement.addEventListener("click", openCreateFreshDeskTicket);
                    }

                } else {
                    $("#section_" + menuItem).html(createButtonHtml + '<br/>No recent Freshdesk tickets');
                    buttonElement = document.getElementById("createUserBtn1");
                    if (buttonElement) {
                        buttonElement.addEventListener("click", openCreateFreshDeskTicket);
                    }
                }
            }
        });
    };

    var displayFreshDeskTicketsAccount = function (menuItem, url) {
        var $this = $("#account-modal-" + menuItem + "-count");
        $.ajax({
            type: "GET",
            url: url,
            cache: false,
            contentType: "application/json"
        }).done(function (data) {
            var accountModalMenuItemCount = $("#account-modal-" + menuItem + "-count");
            if (data === "") {
                $this.html(0);
                $this.show();
                $("#account-section-" + menuItem).html('<br/>No recent Freshdesk tickets');
            } else {
                $this.html(data.length);
                $this.show();

                var dHtml = "";
                for (var i = 0; i < data.length; i++) {
                    dHtml +=
                        "<div style='clear: both; border-bottom: 2px solid #545454;background-color: #ffffff; margin-top: 5px; margin-bottom: 5px;padding: 5px;'>";
                    dHtml +=
                        "<div><b>Ticket ID# <a style='text- decoration: none; font-weight: bold;' href='javascript: window.ePlus.modules.support.openFreshdeskTicket(" +
                        data[i].id +
                        ");'>" +
                        data[i].id +
                        "</a></b>&nbsp;&bull;&nbsp;" +
                        data[i].createdAt +
                        "&nbsp;&bull;&nbsp;" +
                        window.ePlus.modules.support.getFreshdeskStatus(data[i].status) +
                        "</div>";
                    dHtml += "<div><b>Subject:</b> " + data[i].subject + "</div>";
                    dHtml += "<div style='max-height: 150px; overflow: auto;'>" +
                        data[i].description +
                        "</div>";
                    dHtml += "</div>";
                }
                var buttonElement;
                var createButtonHtml = '<button id="createAcctBtn1">New Ticket</button><br/>';
                if (dHtml !== "") {
                    $("#account-section-" + menuItem).html(createButtonHtml + dHtml);
                    buttonElement = document.getElementById("createAcctBtn1");
                    if (buttonElement) {
                        buttonElement.addEventListener("click", openCreateFreshDeskTicket);
                    }

                } else {
                    $("#account-section-" + menuItem).html(createButtonHtml + '<br/>No recent Freshdesk tickets');
                    buttonElement = document.getElementById("createAcctBtn1");
                    if (buttonElement) {
                        buttonElement.addEventListener("click", openCreateFreshDeskTicket);
                    }
                }
            }
        });
    };

    fd.setDefaults = setDefaults;
    fd.createFreshDeskTicket = createFreshDeskTicket;
    fd.getDefaultEmailAddress = getDefaultEmailAddress;
    fd.getFreshDeskTicketsforUser = getFreshDeskTicketsforUser;
    fd.getFreshDeskTicketsforAccount = getFreshDeskTicketsforAccount;


})(window.ePlus.modules.freshDesk = window.ePlus.modules.freshDesk || {}, jQuery, _);

;
;
ePlus.modules.eventGrids = (function () {
    var listView = window.ePlus.modules.listView;
    var initEventGridRequests = function () {
        var orgId = window.getListViewProperty('selectedOrgID');

        if (orgId === null || orgId === '') {
            return;
        }

        $('.request-information-container').addClass('clickable').on('click', function () {
            var eventId = $(this).data('event-id');
            var sku = $(this).attr('data-sku');
            var campaignId = $(this).data('campaign-id');

            if (listView.getListViewProperty('resultType') === window.getEnumValue('resultType', 'TITLEPUBLICITYCAMPAIGN')) {
                campaignId = listView.getListViewProperty('itemID');
            }

            openPublicityCampaignEventRequest(campaignId, eventId, sku, 0, false);
        });
    }

    var getEventGridRequestUi = function () {
        getEventGridRequests();
    }

    function doLoadMostRecentRequest() {
        return ePlus.modules.listView.isResultType('TITLE_INDIVIDUAL') || ePlus.modules.listView.isResultType('TITLE_CATALOG');
    }

    function getEventGridRequests() {
        var visibleSkus = $('.activeRow').map(function(_,el){
            return $(el).attr('data-item');
        }).get();
        var param = $.param({ skus: visibleSkus });
        var orgId = listView.getListViewProperty('selectedOrgID');

        if (!visibleSkus) {
            return;
        }

        hideAllEventGridRequests(visibleSkus);
        
        $.ajax({
            type: 'GET',
            url: '/api/publicitys/campaigns/' + orgId + '/requests/skus',
            contentType: 'application/json',
            data: param
        })
            .done(function(tuples) {
                tuples.forEach(function(tuple) {
                    updateEventGridRequestDate(tuple);
                    updateEventGridRequestDescription(orgId, tuple);
                });
            }).fail(function() {
                hideAllEventGridRequests(visibleSkus);
            });
    }

    function hideAllEventGridRequests(visibleSkus) {
        visibleSkus.forEach(function(sku) {
            toggleEventGridRequestDate(sku, true);
            toggleEventGridRequestVisibility(sku, true);
        });
    }

    function updateEventGridRequestDate(tuple) {
        var $requestedDate = $('#publicityEventDate' + tuple.sku);
        var doHideEventGridRequestDate = tuple.requestDate === "";

        toggleEventGridRequestDate(tuple.sku, doHideEventGridRequestDate);
        $requestedDate.html(tuple.requestDate);
    }

    function toggleEventGridRequestDate(sku, doHideEventGridRequestDate) {
        $('#campaignEventAvailable_' + sku).toggle(!doHideEventGridRequestDate);
        $('#eventIcon_' + sku).toggle(!doHideEventGridRequestDate);
    }

    function updateEventGridRequestDescription(orgId, tuple) {
        var $campaignRequestTitleInfo = $('#campaignRequestTitleInfo_' + tuple.sku);
        var url =
            "/GetTreelineControl.aspx?controlName=/uc/PublicityCampaign/TitleFrame_PublicityCampaign_Request.ascx&sku=" +
                tuple.sku +
                "&orgId=" +
                orgId;
        var doHideEventGridRequest = tuple.requestDate === "";

        toggleEventGridRequestVisibility(tuple.sku, doHideEventGridRequest);
        if (doHideEventGridRequest) {
            $campaignRequestTitleInfo.html("");
            $('#publicityEventArrow' + tuple.sku).removeClass('icon-drop-down-icon icon-drop-up-icon-01');
        } else {
            $campaignRequestTitleInfo.load(url);
            $('#publicityEventArrow' + tuple.sku).removeClass('icon-drop-up-icon-01').addClass('icon-drop-down-icon');
            $('#campaignRequestContainer_' + tuple.sku).addClass('hidden');
        }
    }

    function toggleEventGridRequestVisibility(sku, doHideEventGridRequest) {
        $('#campaignRequestContainer_' + sku).toggleClass('hidden', doHideEventGridRequest);
    }

    var toggleEventGridDescriptionVisiblity = function (sku) {
        var $eventGridRequestArrow = $('#publicityEventArrow' + sku);
        var doHideEventGridDescription = $eventGridRequestArrow.hasClass('icon-drop-down-icon');
        
        $('#campaignRequestContainer_' + sku).toggleClass('hidden', !doHideEventGridDescription);
        $eventGridRequestArrow.toggleClass('icon-drop-down-icon icon-drop-up-icon-01');
    }

    return {
        getEventGridRequestUi: getEventGridRequestUi,
        initEventGridRequests: initEventGridRequests,
        toggleEventGridDescriptionVisiblity: toggleEventGridDescriptionVisiblity
    }
})();;
;
window.ePlus.modules.userAffiliation = (function () {
    var createAffiliation = function (newAffiliation, callback, alwaysCallback) {
        if (!newAffiliation) return;

        $.ajax({
            type: 'POST',
            dataType: 'json',
            contentType: 'application/json; charset=utf-8',
            data: JSON.stringify(newAffiliation),
            url: '/api/affiliations'
        }).done(callback).fail(window.ePlus.util.ajaxHelper.handleGenericAjaxFail).always(alwaysCallback);
    };

    var updateAffiliation = function (existingAffiliation, callback, alwaysCallback) {
        if (!existingAffiliation || !existingAffiliation.id) return;
        $.ajax({
            type: 'PUT',
            dataType: 'json',
            contentType: 'application/json; charset=utf-8',
            data: JSON.stringify(existingAffiliation),
            url: '/api/affiliations/' + existingAffiliation.id
        }).done(callback).fail(window.ePlus.util.ajaxHelper.handleGenericAjaxFail).always(alwaysCallback);
    };

    var deleteAffiliation = function (affiliationId, callback) {
        $.ajax({
            type: 'DELETE',
            dataType: 'json',
            contentType: 'application/json; charset=utf-8',
            url: 'api/affiliations/' + affiliationId
        }).done(callback).fail(window.ePlus.util.ajaxHelper.handleGenericAjaxFail);
    };

    return {
        createAffiliation: createAffiliation,
        updateAffiliation: updateAffiliation,
        deleteAffiliation: deleteAffiliation
    };
})();;
;
window.ePlus.modules.legacyAffiliation = (function () {
    var updateAffiliation = function (existingAffiliation, callback, alwaysCallback) {
        if (!existingAffiliation || !existingAffiliation.id) return;

        $.ajax({
            type: 'PUT',
            dataType: 'json',
            contentType: 'application/json; charset=utf-8',
            data: JSON.stringify(existingAffiliation),
            url: '/api/community/legacy/' + existingAffiliation.id
        })
            .done(callback)
            .fail(window.ePlus.util.ajaxHelper.handleGenericAjaxFail)
            .always(alwaysCallback);
    };

    return {
        updateAffiliation: updateAffiliation,
    };
})();;
;
window.ePlus.modules.communityWizard = (function () {
    var actions = {
        close: 'wizard::community::close',
        remindMeLater: 'wizard::community::remind-me-later',
        doNotShowMeAgain: 'wizard::community::do-not-show-me-again',
        complete: 'wizard::community::complete',
    }

    var closeWizard = function () {
        closeAllDialogs();
    }

    var closeWizardAndReloadPage = function () {
        closeWizard();
        reloadPage();
    };

    var remindMeLater = function () {
        $.ajax({
            type: 'PUT',
            url: '/api/affiliations/wizard/remindMeLater'
        }).done(function () {
            closeWizardAndReloadPage();
        }).fail(function (ret) {
            console.error(ret);
        });
    };

    var doNotShowMeAgain = function () {
        $.ajax({
            type: 'PUT',
            url: '/api/affiliations/wizard/doNotShowMeAgain'
        }).done(function () {
            closeWizardAndReloadPage();
        }).fail(function (ret) {
            console.error(ret);
        });
    };

    var complete = function () {
        $.ajax({
            type: 'PUT',
            url: '/api/affiliations/wizard/complete'
        }).done(function () {
            closeWizardAndReloadPage();
        }).fail(function (ret) {
            console.error(ret);
        });
    };

    var messageHandler = function (event) {
        var data = JSON.parse(event.data);
        var action = data.action;

        switch (action) {
            case actions.close:
                closeWizardAndReloadPage();
                break;
            case actions.remindMeLater:
                remindMeLater();
                break;
            case actions.doNotShowMeAgain:
                doNotShowMeAgain();
                break;
            case actions.complete:
                complete();
                break;
            default:
                console.error('invalid wizard action: ' + action);
        }
    };

    return {
        closeWizard: closeWizard,
        closeWizardAndReloadPage: closeWizardAndReloadPage,
        remindMeLater: remindMeLater,
        doNotShowMeAgain: doNotShowMeAgain,
        complete: complete,
        messageHandler: messageHandler
    };
})();;
;
window.ePlus.modules.e360 = (function () {
    var actions = {
        promoteTitle: 'e360::promote::title'
    };

    var promoteTitle = function (sku) {
        openPromoteModal(sku);
    };

    var messageHandler = function (event) {
        var data = JSON.parse(event.data);
        var action = data.action;
        var sku = data.sku;

        switch (action) {
            case actions.promoteTitle:
                promoteTitle(sku);
                break;
            default:
                console.error('invalid e360 action: ' + action);
        }
    };

    return {
        messageHandler: messageHandler,
        promoteTitle: promoteTitle
    };
})();;
;
window.ePlus.modules.userTransfer = (function () {
    var invertAccounts = function () {
        if (!$('#transfer-to').data('orgid')) {
            alert(getRes('user_transfer_select_account'));
        } else {
            var sourceOrgId = $('#transfer-from').data('orgid');
            var targetOrgId = $('#transfer-to').data('orgid');
            var sourceOrgName = $('#transfer-from').html();
            var targetOrgName = $('#transfer-to').html();
            $('#transfer-from').html(targetOrgName).data('orgid', targetOrgId);
            $('#transfer-to').html(sourceOrgName).data('orgid', sourceOrgId);
            populateTransferDetails();
        }
    };

    var selectTransferToOrg = function (targetOrgId) {
        var sourceOrgId = $('#transfer-from').data('orgid');
        if (sourceOrgId !== targetOrgId) {
            $('#transfer-to').html($('.account-name-' + targetOrgId).html() + ' (' + targetOrgId + ')').data('orgid', targetOrgId).webuiPopover('hide');
            populateTransferDetails();
        } else {
            $('#transfer-to').webuiPopover('hide');
            alert(getRes('user_transfer_from_cant_be_to'));
        }
    };

    var populateTransferDetails = function () {
        var sourceOrgId = $('#transfer-from').data('orgid');
        var targetOrgId = $('#transfer-to').data('orgid');
        $('#transfer-details').html('<div class="pad-top-5">' + getRes('loading') + '</div>');
        $.url = '/GetTreelineControl.aspx?controlName=/uc/support/transferUsersDetail.ascx&targetOrgId=' + targetOrgId + '&sourceOrgId=' + sourceOrgId;
        $('#transfer-details').load($.url, function () {
            $('#transfer-next').removeClass('hidden');
        });
    }

    var transferGoBack = function () {
        $('#transfer-progress').addClass('hidden');
        $('#transfer-details').removeClass('hidden');
        $('#transfer-back').addClass('hidden');
        $('#transfer-next').removeClass('hidden');
        $('#transfer-start').addClass('hidden');
        $('.transfer-progress-row').remove();
    }

    var transferGoNext = function () {
        var hasUsersToTransfer = false;
        $('.transfer-check').each(function () {
            var $elem = $(this);
            if ($elem.hasClass('box_checked')) {
                hasUsersToTransfer = true;
                var appUserId = $elem.data('appuserid');
                $('#transfer-progress-table tr:last').after('<tr class="transfer-progress-row"><td>' + $("#transfer-user-" + appUserId).html() + '</td><td>' + getTransferUserHtmlProgressBar(appUserId) + '</td><td><div id="complete-check-' + appUserId + '" class="onboard-complete icon-mark-icon hidden pad-left-5 color-dark-green"></div></td></tr>');
            }
        });
        if (hasUsersToTransfer) {
            $('#transfer-progress').removeClass('hidden');
            $('#transfer-details').addClass('hidden');
            $('#transfer-back').removeClass('hidden');
            $('#transfer-next').addClass('hidden');
            $('#transfer-start').removeClass('hidden');
        } else {
            alert(getRes('select_users_to_transfer'));
        }
    }

    var transferStart = function () {
        $('#transfer-start').addClass('hidden');
        $('#transfer-back').addClass('hidden');
        var groupIds = [];
        $('.transfer-group-check.box_checked').each(function () {
            var groupId = $(this).data('groupid');
            groupIds.push(groupId);
        });
        $('.transfer-check.box_checked').each(function () {
            var transferInfo = {};
            transferInfo.appUserId = $(this).data('appuserid');
            var $transferUser = $('#transfer-user-' + transferInfo.appUserId);
            transferInfo.newOrgId = $('#transfer-to').data('orgid');
            transferInfo.oldOrgId = $transferUser.data('orgid');
            transferInfo.oldUserId = $transferUser.data('userid');
            transferInfo.groupIds = groupIds;

            $.ajax({
                type: 'POST',
                url: 'api/supportTools/userTransferCheck',
                dataType: 'json',
                contentType: 'application/json; charset=utf-8',
                data: JSON.stringify(transferInfo)
            }).done(function (data) {
                transferInfo.newUserId = data;
                transferInfo.type = getEnumValue('userTransferType', 'CATALOG');
                transferOptions(transferInfo, transferInfo.appUserId);
                transferInfo.type = getEnumValue('userTransferType', 'USER');
                transferOptions(transferInfo, transferInfo.appUserId);
                transferInfo.type = getEnumValue('userTransferType', 'WORK');
                transferOptions(transferInfo, transferInfo.appUserId);
                transferInfo.type = getEnumValue('userTransferType', 'GROUPS');
                transferOptions(transferInfo, transferInfo.appUserId);
                transferInfo.type = getEnumValue('userTransferType', 'MARKUPSOURCE');
                transferOptions(transferInfo, transferInfo.appUserId);
                transferInfo.type = getEnumValue('userTransferType', 'ORDERS');
                transferOptions(transferInfo, transferInfo.appUserId);
            });
        });
    }

    var transferOptions = function (transferInfo, appUserId) {
        var type = transferInfo.type;
        $.ajax({
            type: 'POST',
            url: 'api/supportTools/userTransfer',
            dataType: 'json',
            contentType: 'application/json; charset=utf-8',
            data: JSON.stringify(transferInfo)
        }).done(function (data) {
            incrementTransferUserBar(appUserId);
            if (type === getEnumValue('userTransferType', 'USER')) {
                if ($('#account-modal-org').length > 0) {
                    window.ePlus.modules.support.getAccountUserCount(getEnumValue('supportAccountMenu', 'USERS'), $('#account-modal-org').html());
                }
                window.ePlus.modules.dashboard.refreshWidgetsWithResultType(getEnumValue('resultType', 'SUPPORTACCOUNTS'));
            };
        });
    }

    var incrementTransferUserBar = function (appUserId) {
        var incrementPercent = 16.67;
        var $elem = $('#pg-bar-' + appUserId);
        var currentWidth = $elem.data('width');
        if (currentWidth < 100) {
            var newWidth = currentWidth + incrementPercent;
            $elem.css('width', newWidth + '%')
                .data('width', newWidth);
            if (newWidth >= 100) {
                $('#complete-check-' + appUserId).removeClass('hidden');
            }
        }
    }

    var getTransferUserHtmlProgressBar = function (appUserId) {
        return '<div class="border-1"><div id="pg-bar-' + appUserId + '" class="bg-red progress-bar" data-width="0" style="width:0;"></div></div>';
    }

    return {
        invertAccounts: invertAccounts,
        selectTransferToOrg: selectTransferToOrg,
        populateTransferDetails: populateTransferDetails,
        transferGoBack: transferGoBack,
        transferGoNext: transferGoNext,
        transferStart: transferStart,
        transferOptions: transferOptions,
        incrementTransferUserBar: incrementTransferUserBar,
        getTransferUserHtmlProgressBar: getTransferUserHtmlProgressBar
    };
})();;
;
window.ePlus.modules.dashboard = (function () {
    var pollItInterval;
    var toggleFilterInsetVisibility = function (laneId, filterDisplay) {
        if (filterDisplay) {
            $('#dash-header-' + laneId).html('<b>' + getRes('filter') + '</b>: ' + filterDisplay);
            $('#dash-saved-filter-' + laneId).removeClass('hidden');
        } else {
            $('#dash-header-' + laneId).html('');
            $('#dash-saved-filter-' + laneId).addClass('hidden');
        }
    };

    var initializeDashboard = function () {
        $('#lane-prefs').webuiPopover({
            type: 'async',
            cache: false,
            backdrop: true,
            url: '/GetTreelineControl.aspx?controlName=/uc/dashboard_v2/AddDashLane.ascx'
        });

        $('.masonry', $('#widget-area')).sortable({
            connectWith: '.masonry',
            handle: '.dlMove',
            placeholder: 'targetMove',
            update: function () {
                serializeDashboard();
            }
        });

        initializeLaneInteractivity();

        pollItInterval = window.setInterval(function () {
            window.ePlus.modules.dashboard.pollIt();
        }, 60000);
    }

    var clearPollItInterval = function () {
        if (pollItInterval) {
            clearInterval(pollItInterval);
        }
    }

    var toggleWidgetEdits = function (laneId, state) {
        $('#remove-lane-' + laneId).toggle(state);
        $('#change-lane-' + laneId).toggle(state);
        initializeChangeMenu(laneId);
    }

    var removeLane = function (laneId) {
        var widgetId = $('#dashLane_' + laneId).attr('data-widgetid');
        $('#dashLane_' + laneId).slideUp().remove();
        serializeDashboard();

        $.getJSON('/getJSONData.aspx?builder=DeleteWidgetLane', { widgetId: widgetId });
    }

    var initializeChangeMenu = function (laneId) {
        $('#change-lane-' + laneId).webuiPopover({
            type: 'async',
            cache: false,
            backdrop: true,
            url: '/GetTreelineControl.aspx?controlName=/uc/dashboard_v2/AddDashLane.ascx&laneId=' + laneId
        });
    }

    var serializeDashboard = function () {
        var resultList = '';
        $('.dashLane', $('#widget-parent')).each(function (i, obj) {
            var $elem = $(this);
            if (resultList !== '') { resultList += ';' }
            resultList += buildColumnSettingString(i, $elem.attr('data-widgetid'), $elem.attr('data-resulttype'));
        });

        $.getJSON('/getJSONData.aspx?builder=SaveUserDashboard', { dashString: resultList });
    }

    var buildColumnSettingString = function (sequence, widgetId, resultType) {
        return widgetId + ',' + sequence + ',' + resultType;
    }

    var changeLane = function (laneId, resultType) {
        var $elem = $('#dashLane_' + laneId);
        $elem.attr('data-resulttype', resultType);
        $.url = '/GetTreelineControl.aspx?controlName=/uc/dashboard_v2/DashLane.ascx&resultType=' + resultType + '&laneID=' + laneId;
        $('#dashLane_' + laneId).load($.url);
        $('#change-lane-' + laneId).webuiPopover('hide');
        $.getJSON('/getJSONData.aspx?builder=ChangeWidgetLane',
            {
                resultType: resultType,
                widgetID: $elem.attr('data-widgetid')
            });
    }

    var pollIt = function () {
        $('.dashLane', $('#widget-parent')).each(function (i, obj) {
            var $elem = $(this);
            if ($elem.hasClass('poll')) {
                var settings = {
                    laneId: $elem.attr('data-laneid') * 1,
                    resultType: $elem.attr('data-resulttype') * 1,
                    widgetId: $elem.attr('data-widgetid') * 1,
                    refresh: 'poll'
                }

                initialDashLoad(settings);
            }
        });
    }

    var initialDashLoad = function (settings) {
        var widgetUrl = getWidgetUrl(settings.resultType, settings.laneId, settings.widgetId);

        if (widgetUrl) {
            if (!settings.refresh) {
                $('#dashContent_' + settings.laneId).html('<div style="height:30px">&nbsp;</div>');
            }
            $('#dashLaneMain_' + settings.laneId).addClass('progressBackground');
            $('#dashContent_' + settings.laneId).load(widgetUrl, function () {
                $('#dashLaneMain_' + settings.laneId).removeClass('progressBackground');
            });
        } else {
            if (!settings.refresh) {
                $('#dashContent_' + settings.laneId).html('<div style="height:30px">&nbsp;</div>');
            }
            if (settings.widgetId === 0) {
                settings.widgetId = $('#dashLane_' + settings.laneId).attr('data-widgetID');
            }
            populateDashLane(settings.resultType, settings.laneId, settings.widgetId);
        }
    }

    var refreshWidgetsWithResultType = function (resultType) {
        $('.dashLane_' + resultType, $('#interiorPageContent')).each(function (i, obj) {
            var $this = $(this);

            var settings = {
                laneId: $this.attr('data-laneid'),
                resultType: resultType,
                widgetId: $this.attr('data-widgetid'),
                refresh: true
            }

            initialDashLoad(settings);
        });
    }

    var addLane = function (resultType) {
        var newLane = $('.dashLane').length + 1;
        $('#widget-parent').prepend("<div class='hidden item dashLane dashLane_" + resultType + "' data-laneid='" + newLane + "' data-resulttype='" + resultType + "' id='dashLane_" + newLane + "'></div>");
        $.url = '/GetTreelineControl.aspx?controlName=/uc/dashboard_v2/DashLane.ascx&resultType=' + resultType + '&laneID=' + newLane;
        saveNewlyAddedLane(resultType, newLane, $.url);
    }

    var saveNewlyAddedLane = function (resultType, newLane, url) {
        var resultList = '0,0,' + resultType;
        $.getJSON('/getJSONData.aspx?builder=SaveUserDashboard_NewLane',
            { lane: resultList },
            function (data) {
                if (data.code == 'OK') {
                    $('#dashLane_' + newLane).attr('data-widgetID', data.data);
                    serializeDashboard();
                    $('#dashLane_' + newLane).load(url, function () {
                        $('#dashLane_' + newLane).removeClass('hidden');
                        initializeLaneInteractivity();
                    });
                    $('#lane-prefs').webuiPopover('hide');
                }
            });
    }

    var initializeLaneInteractivity = function () {
        $('.dashLaneNavigation', $('#widget-parent')).hover(function () {
            var laneId = $(this).data('laneid');
            toggleWidgetEdits(laneId, true);
            $('.prefPop_' + laneId, $('#widget-area')).webuiPopover({
                type: 'async',
                cache: false,
                backdrop: true,
                url: '/GetTreelineControl.aspx?controlName=/uc/dashboard_v2/prefs/MainDashPrefs.ascx&laneID=' + laneId + '&resultType=' + $('#dashLane_' + laneId).attr('data-resultType') + '&widgetID=' + $('#dashLane_' + laneId).attr('data-widgetID')
            });
        }, function () {
            var laneId = $(this).data('laneid');
            toggleWidgetEdits(laneId, false);
        });

        $('.dashLaneBody', $('#widget-parent')).hover(function () {
            var laneId = $(this).data('laneid');
            toggleWidgetEdits(laneId, true);
        }, function () {
            var laneId = $(this).data('laneid');
            toggleWidgetEdits(laneId, false);
        });

        $('.dash-change', $('#widget-parent')).click(function () {
            var laneId = $(this).data('laneid')
            initializeChangeMenu(laneId);
        });

        $('.dash-remove', $('#widget-parent')).click(function () {
            var laneId = $(this).data('laneid')
            removeLane(laneId);
        });
    }

    var restoreDefaultDashOrder = function () {
        $('#lane-prefs').webuiPopover('hide');
        $.getJSON('/getJSONData.aspx?builder=RestoreDashDefault', null,
            function (data) {
                if (data.code == 'OK') {
                    goToDashboard();
                }
            });
    }

    var saveWidgetPreference = function (settings) {
        $.getJSON('/getJSONData.aspx?builder=SaveWidgetPreference',
            {
                widgetID: settings.widgetId,
                prefType: settings.prefType,
                pref: settings.prefValue
            },
            function (data) {
                if (data.code == 'OK') {
                    populateDashLane(settings.resultType, settings.laneId, settings.widgetId);
                }
            });
        $('.prefPop_' + settings.laneId, $('#widget-parent')).webuiPopover('hide');
        $('#savedFilterPrefs').webuiPopover('hide');
    }

    var populateDashLane = function (resultType, laneId, widgetId, more) {
        var $dashContent = $('#dashContent_' + laneId),
            userId = null,
            $popover = null;

        $('#viewAll_' + laneId).unbind();
        var widgetUrl = getWidgetUrl(resultType, laneId, widgetId);
        if (!widgetUrl) {
            $.url = '/GetTreelineControl.aspx?controlName=/uc/dashboard_v2/DashItems.ascx&resultType=' + resultType + '&laneID=' + laneId + '&widgetID=' + widgetId;
        } else {
            $.url = widgetUrl;
        }

        if (!widgetId) {
            var popoverId = $dashContent.data('popoverid');

            userId = $dashContent.data('userid');
            $popover = $('#' + popoverId);

            $.url += '&userId=' + userId;
            $.url += '&popoverId=' + popoverId;
        }

        $('#dashLaneMain_' + laneId).addClass('progressBackground');
        if ($('#dashLane_' + laneId).hasClass('showMore')) {
            $.url += '&more=1';
        }

        if ($popover) {
            WebuiPopovers.updateContentAsync($popover, $.url);
        } else {
            window.ePlus.modules.dashboard.widgets.create({
                uri: $.url,
                laneId: laneId,
                resultType: resultType,
                widgetId: widgetId
            });
        }
    }

    var declineNoticeFriendInvite = function (friendId, noticeId, laneId) {
        $.post('/getJSONData.aspx?builder=DeclineInvite',
            {
                friendID: friendId
            },
            function (data) {
                if (data.code === 'OK') {
                    refreshDashLaneAfterAction(laneId);
                }
            }, 'json');
        return false;
    }

    var acceptNoticeFriendInvite = function (friendId, noticeId, laneId) {
        $.post('/getJSONData.aspx?builder=AcceptInvite',
            {
                friendID: friendId
            },
            function (data) {
                if (data.code === 'OK') {
                    refreshDashLaneAfterAction(laneId);
                }
            }, 'json');
        return false;
    }

    var markNoticeComplete = function (noticeId, noticeType, laneId) {
        var values = {
            status: 1,
            noticeId: noticeId
        };

        if (laneId || laneId == 0) {
            $('#dashLaneMain_' + laneId).addClass('progressBackground');
        }

        $.post('/getJSONData.aspx?builder=ChangeUserNoticeStatus', values, function (data) {
            if (data.code === 'OK') {
                if (laneId || laneId == 0) {
                    refreshDashLaneAfterAction(laneId);
                    async.retry(2, window.ePlus.modules.header.getHeaderNoticeCount());
                }
            }
            else {
                alert(data.text);
            }
        }, 'json');
    }

    var acceptSignUpRequest = function (emailAdd, orgId, laneId) {
        savingModal(getRes('saving'));
        var values = {
            email: emailAdd
        };

        $.ajax({
            type: 'POST',
            url: 'api/organizations/' + orgId + '/users/requests/signups/approve',
            dataType: 'json',
            contentType: 'application/json; charset=utf-8',
            data: JSON.stringify(emailAdd)
        })
            .done(function () {
                $.post('/getJSONData.aspx?m=User&builder=SendUserRequestApprovalEmail', values, function (data) {
                    if (data.code == 'SUCCESS') {
                        refreshDashLaneAfterAction(laneId);
                    }
                    else {
                        alert(data.text);
                    }
                }, 'json');
            })
            .fail(function () {
                modalAlert(window.ePlus.resources.getRes("error_unexpected"));
            });
        
    }

    var declineSignUpRequest = function (emailAdd, noticeId, laneId) {
        savingModal(getRes('saving'));
        var values = {
            Email: emailAdd
        };

        $.post('/getJSONData.aspx?m=User&builder=DeclineSignUpUserRequest', values, function (data) {
            if (data.code == 'SUCCESS') {
                refreshDashLaneAfterAction(laneId);
            }
        }, 'json');
    }

    var refreshDashLaneAfterAction = function (laneId) {
        var $dashLane = $('#dashLane_' + laneId);
        var resultType = $dashLane.data('resulttype');
        var widgetId = $dashLane.data('widgetid');

        populateDashLane(resultType, laneId, widgetId);
    }

    var getWidgetUrl = function (resultType, laneId, widgetId) {
        var url = '';
        switch (resultType) {
            case getEnumValue('resultType', 'COLLABORATIVELISTS'):
                url = '/uc/dashboard_v2/widgetPages/widgetCollaborativeLists.ascx';
                break;
            case getEnumValue('resultType', 'SUPPORTSTOREDPROCEDURES'):
                url = '/uc/dashboard_v2/widgetPages/widgetSupportStoredProcedures.ascx';
                break;
            case getEnumValue('resultType', 'PUBLISHERRECENTVIEW'):
                url = '/uc/organization/publisherSelect.ascx';
                break;
            case getEnumValue('resultType', 'CATALOG_SEARCH'):
                url = '/uc/listviews/menus/ListView_Pop_Find.ascx&Source=' + getEnumValue('catalogSearchSource', 'WIDGET');
                break;
            case getEnumValue('resultType', 'PUBLISHER_VISIBLE_READERS'):
                url = '/uc/dashboard_v2/widgetPages/widgetPublishers.ascx';
                break;
            case getEnumValue('resultType', 'SUPPORTNPS'):
                url = '/uc/dashboard_v2/widgetPages/widgetNPS.ascx';
                break;
            case getEnumValue('resultType', 'SUPPORTNEWUSERS'):
                url = '/uc/dashboard_v2/widgetPages/widgetNewUsers.ascx';
                break;
            case getEnumValue('resultType', 'SUPPORTRECEIVABLES'):
                url = '/uc/dashboard_v2/widgetPages/widgetReceivables.ascx';
                break;
            case getEnumValue('resultType', 'SUPPORTLONGLOADTIMES'):
                url = '/uc/dashboard_v2/widgetPages/widgetLoadTimes.ascx';
                break;
            case getEnumValue('resultType', 'SUPPORTANALYTICFRESHNESS'):
                url = '/uc/dashboard_v2/widgetPages/widgetAnalyticsFreshness.ascx';
                break;
            case getEnumValue('resultType', 'SUPPORTRECURRINGSUBSCRIPTIONS'):
                url = '/uc/dashboard_v2/widgetPages/widgetSubscriptions.ascx';
                break;
            case getEnumValue('resultType', 'SUPPORTLIBRARYMAP'):
                url = '/uc/dashboard_v2/widgetPages/widgetLibraries.ascx';
                break;
            case getEnumValue('resultType', 'SUPPORTSALESPIPELINE'):
                url = '/uc/dashboard_v2/widgetPages/widgetSalesPipeline.ascx';
                break;
            case getEnumValue('resultType', 'SUPPORTONBOARDING'):
                url = '/uc/dashboard_v2/widgetPages/widgetOnboarding.ascx';
                break;
            case getEnumValue('resultType', 'SUPPORTANALYTICSUSAGE'):
                url = '/uc/dashboard_v2/widgetPages/widgetAnalyticsUsage.ascx';
                break;
            case getEnumValue('resultType', 'SCORECARDSUMMARY'):
                url = '/uc/dashboard_v2/widgetPages/widgetScorecard.ascx';
                break;
            case getEnumValue('resultType', 'SUPPORTLIBRARYSUMMARY'):
                url = '/uc/dashboard_v2/widgetPages/widgetLibraryDash.ascx';
                break;
            case getEnumValue('resultType', 'SUPPORTCUSTOMERMANAGEMENT'):
                url = '/uc/dashboard_v2/widgetPages/widgetCustomerManagement.ascx';
                break;
            case getEnumValue('resultType', 'SUPPORTFOLLOWUPS'):
                url = '/uc/dashboard_v2/widgetPages/widgetFollowUps.ascx';
                break;
            case getEnumValue('resultType', 'SUPPORTREVENUE'):
                url = '/uc/dashboard_v2/widgetPages/widgetRevenue.ascx';
                break;
            case getEnumValue('resultType', 'SUPPORT360USERUSAGE'):
                url = '/uc/dashboard_v2/widgetPages/widget360Usage.ascx';
                break;
            case getEnumValue('resultType', 'SUPPORT360USERNEWCAMPAIGNS'):
                url = '/uc/dashboard_v2/widgetPages/widget360Campaigns.ascx';
                break;
            case getEnumValue('resultType', 'SUPPORTPRCSFULFILLED'):
            case getEnumValue('resultType', 'SUPPORTINSERTTRENDS'):
            case getEnumValue('resultType', 'SUPPORTFEATUREDTITLETRENDS'):
                url = '/uc/dashboard_v2/widgetPages/widgetTrendLine.ascx';
                break;
            case getEnumValue('resultType', 'SUPPORTACCOUNTS'):
                url = '/uc/dashboard_v2/widgetPages/WidgetAccounts.ascx';
                break;
            case getEnumValue('resultType', 'SUPPORTMOPS'):
                url = '/uc/dashboard_v2/widgetPages/widgetMops.ascx';
                break;
            case getEnumValue('resultType', 'PEOPLE_ALL_AFFILIATES'):
                url = '/uc/dashboard_v2/widgetPages/widgetCommunities.ascx';
                break;
            default:
                url = null;
        }

        if (url != null) {
            return '/GetTreelineControl.aspx?controlName=' + url + '&laneID=' + laneId + '&widgetID=' + widgetId + '&resultType=' + resultType;
        } else {
            return null;
        }
    }

    var changeAddSection = function (userProfile) {
        $('.dash-option-section').addClass('hidden');
        $('.userModalArrow').addClass('hidden');
        $('.labelContent').removeClass('selectedLabel');
        $('#dash-option-section-' + userProfile).removeClass('hidden');
        $('#modal_' + userProfile + '-arrow').removeClass('hidden');
        $('#modal_' + userProfile + '-label').addClass('selectedLabel');
    }

    var getCurrentViewCount = function (resultType) {
        var currentCount = $('.dashLane_' + resultType).length;
        if (currentCount == 1) {
            $('#currentCheck_' + resultType).removeClass('hidden');
            $('#remove-result-' + resultType).removeClass('hidden');
        } else if (currentCount > 1) {
            $('#currentCount_' + resultType).html(currentCount).removeClass('hidden');
        }
    }

    var removeResultTypeFromDashboard = function (resultType) {
        $('.dashLane_' + resultType).each(function (i, obj) {
            var laneId = $(this).attr('data-laneid');
            removeLane(laneId);
        })
        $('#currentCheck_' + resultType).addClass('hidden');
        $('#currentCount_' + resultType).addClass('hidden');
        $('#remove-result-' + resultType).addClass('hidden');
    }

    return {
        toggleFilterInsetVisibility: toggleFilterInsetVisibility,
        initializeDashboard: initializeDashboard,
        removeLane: removeLane,
        initializeChangeMenu: initializeChangeMenu,
        toggleWidgetEdits: toggleWidgetEdits,
        serializeDashboard: serializeDashboard,
        buildColumnSettingString: buildColumnSettingString,
        changeLane: changeLane,
        pollIt: pollIt,
        clearPollItInterval: clearPollItInterval,
        initialDashLoad: initialDashLoad,
        refreshWidgetsWithResultType: refreshWidgetsWithResultType,
        initializeLaneInteractivity: initializeLaneInteractivity,
        addLane: addLane,
        saveNewlyAddedLane: saveNewlyAddedLane,
        restoreDefaultDashOrder: restoreDefaultDashOrder,
        saveWidgetPreference: saveWidgetPreference,
        populateDashLane: populateDashLane,
        declineNoticeFriendInvite: declineNoticeFriendInvite,
        acceptNoticeFriendInvite: acceptNoticeFriendInvite,
        markNoticeComplete: markNoticeComplete,
        acceptSignUpRequest: acceptSignUpRequest,
        declineSignUpRequest: declineSignUpRequest,
        changeAddSection: changeAddSection,
        getCurrentViewCount: getCurrentViewCount,
        removeResultTypeFromDashboard: removeResultTypeFromDashboard,
        refreshDashLaneAfterAction: refreshDashLaneAfterAction
    };
})();
;
;
window.ePlus.modules.dashboard.widgets = (function () {
    var create = function (settings) {
        var widget = getWidget(settings.resultType);

        $('#dashContent_' + settings.laneId).load(settings.uri, function () {
            $('#dashLaneMain_' + settings.laneId).removeClass('progressBackground');

            if (!widget || typeof widget.initialize !== 'function') return;

            widget.initialize(this, settings);
        });

        return widget;
    };

    var getWidget = function (resultType) {
        var widgets = window.ePlus.modules.dashboard.widgets;
        var widget = null;

        switch (resultType) {
            case getEnumValue('resultType', 'NOTICESALLNEW'):
                widget = widgets.notices;
                break;
        }

        return widget;
    };

    return {
        create: create
    }
})();
;
;
window.ePlus.modules.dashboard.widgets.notices = (function () {
    var dashboard;
    var instance = {};

    var initialize = function (lane, settings) {
        dashboard = window.ePlus.modules.dashboard;
        instance.lane = lane;
        instance.settings = settings;

        initializeNoticeLink();
        initializeMarkNoticeCompleteClick();
        initializeMarkNoticeCompleteHover();

        hideMarkAllNoticesComplete();
    };

    var initializeMarkNoticeCompleteClick = function () {
        $('.notice-mark-complete', instance.lane)
            .off('click')
            .on('click', function () {
                var noticeId = $(this).data('noticeid');
                dashboard.markNoticeComplete(noticeId, null, instance.settings.laneId)
            });
    };

    var initializeMarkNoticeCompleteHover = function () {
        $('.notice', instance.laneId)
            .off('mouseover mouseout')
            .on('mouseover', function () {
                $('.notice-mark-complete', this).show();
            })
            .on('mouseout', function () {
                $('.notice-mark-complete', this).hide();
            });
    };

    var initializeNoticeLink = function () {
        $('.notice-link', instance.laneId)
            .off('click')
            .on('click', function () {
                var hash = $(this).data('hash');
                pageChange(hash);
            });
    };

    var hideMarkAllNoticesComplete = function () {
        if ($('.notice', $('#dashLaneMain_' + instance.settings.laneId)).length === 0) {
            $('#widget-mark-all-as-read-' + instance.settings.laneId).hide();
        }
    }

    var initializeAffiliationJoinLinkClickHandlers = function (joinLinksCssSelector) {
        $(joinLinksCssSelector).on("click", function () {
            var joinCommunityUri = $(this).data("joincommunityuri");
            window.open(joinCommunityUri);
        });
    };

    return {
        initializeAffiliationJoinLinkClickHandlers: initializeAffiliationJoinLinkClickHandlers,
        initialize: initialize
    }
})();
;
(function (c, pako) {

    function compressAndEncodeBase64AndUri(str) {
        var compressedBase64 = compressAndEncodeBase64(str);
        return encodeURIComponent(compressedBase64);
    }
    function compressAndEncodeBase64(str) {
        var compressed = compressString(str);
        return btoa(compressed);
    }
    function compressString(str) {
        var byteArray = toUTF8Array(str);
        var compressedByteArray = pako.gzip(byteArray);
        var compressed = String.fromCharCode.apply(null, compressedByteArray);

        return compressed;
    }
    function decompressBase64UriComponent(compressedBase64UriComponent) {
        var compressedBase64 = decodeURIComponent(compressedBase64UriComponent);

        return decompressBase64(compressedBase64);
    }
    function decompressBase64(compressedBase64) {
        var compressed = atob(compressedBase64);

        return decompressString(compressed);
    }
    function decompressString(compressed) {
        var compressedByteArray = compressed.split('').map(function (e) {
            return e.charCodeAt(0);
        });
        var decompressedByteArray = pako.inflate(compressedByteArray);
        var decompressed = fromUTF8Array(decompressedByteArray);

        return decompressed;
    }
    function toUTF8Array(str) {
        var utf8 = [];
        for (var i = 0; i < str.length; i++) {
            var charcode = str.charCodeAt(i);
            if (charcode < 0x80) utf8.push(charcode);
            else if (charcode < 0x800) {
                utf8.push(0xc0 | (charcode >> 6),
                    0x80 | (charcode & 0x3f));
            }
            else if (charcode < 0xd800 || charcode >= 0xe000) {
                utf8.push(0xe0 | (charcode >> 12),
                    0x80 | ((charcode >> 6) & 0x3f),
                    0x80 | (charcode & 0x3f));
            }
            else {
                i++;
                charcode = 0x10000 + (((charcode & 0x3ff) << 10)
                    | (str.charCodeAt(i) & 0x3ff));
                utf8.push(0xf0 | (charcode >> 18),
                    0x80 | ((charcode >> 12) & 0x3f),
                    0x80 | ((charcode >> 6) & 0x3f),
                    0x80 | (charcode & 0x3f));
            }
        }
        return utf8;
    }
    function fromUTF8Array(utf8) {
        var charsArray = [];
        for (var i = 0; i < utf8.length; i++) {
            var charCode, firstByte, secondByte, thirdByte, fourthByte;
            if ((utf8[i] & 0x80) === 0) {
                charCode = utf8[i];
            }
            else if ((utf8[i] & 0xE0) === 0xC0) {
                firstByte = utf8[i] & 0x1F;
                secondByte = utf8[++i] & 0x3F;
                charCode = (firstByte << 6) + secondByte;
            }
            else if ((utf8[i] & 0xF0) === 0xE0) {
                firstByte = utf8[i] & 0x0F;
                secondByte = utf8[++i] & 0x3F;
                thirdByte = utf8[++i] & 0x3F;
                charCode = (firstByte << 12) + (secondByte << 6) + thirdByte;
            }
            else if ((utf8[i] & 0xF8) === 0xF0) {
                firstByte = utf8[i] & 0x07;
                secondByte = utf8[++i] & 0x3F;
                thirdByte = utf8[++i] & 0x3F;
                fourthByte = utf8[++i] & 0x3F;
                charCode = (firstByte << 18) + (secondByte << 12) + (thirdByte << 6) + fourthByte;
            }

            charsArray.push(charCode);
        }
        return String.fromCharCode.apply(null, charsArray);
    }

    c.compressAndEncodeBase64AndUri = compressAndEncodeBase64AndUri;
    c.compressAndEncodeBase64  = compressAndEncodeBase64;
    c.compressString = compressString;
    c.decompressBase64 = decompressBase64;
    c.decompressBase64UriComponent = decompressBase64UriComponent;
    c.decompressString = decompressString;
})(window.ePlus.util.compression = window.ePlus.util.compression || {}, pako);
;
window.ePlus.util.ajaxHelper = (function () {
    var res = window.ePlus.resources;

    var handleGenericAjaxFail = function (jqXhr, textStatus, error) {
        if (jqXhr.status === 500) {
            alert(res.getRes('error_unexpected'));
        } else {
            alert(error);
        }
    }

    return {
        handleGenericAjaxFail: handleGenericAjaxFail
    };
})();;
window.ePlus.util.urlHelper = new (function () {
    this.getPageHashProperty = function (propertyName) {
        var hash = window.location.hash.substr(1);
        var paramsDictionary = getHashParamsDictionary(hash);
        if (paramsDictionary.hasOwnProperty(propertyName)) {
            return paramsDictionary[propertyName];
        } else {
            return null;
        }
    };   

    var getHashParamsDictionary = function (hash) {
        var paramsDictionary = hash.split('&').reduce(function (result, item) {
            var parts = item.split('=');
            result[parts[0]] = parts[1];
            return result;
        }, {});
        return paramsDictionary;
    };

    this.getReturnLoginUrl = function (hash) {
        var url = '/#Login';
        var returnUrl = getReturnUrl(hash);

        if (returnUrl) {
            url += '&returnUrl=' + returnUrl;
        }

        return url;
    };

    var getReturnUrl = function (hash) {
        var paramName = 'returnUrl=';
        var paramIndex = hash.indexOf(paramName);

        if (paramIndex === -1) {
            var returnHash = removeLeadingHash(hash);
            return encodeURIComponent(returnHash);
        } else {
            return hash.substr(paramIndex + paramName.length);
        }
    };

    var removeLeadingHash = function (hash) {
        return _.isString(hash) && hash.length > 0 ? hash.substring(1) : null;
    }
})();;
window.ePlus.util.dom = new (function () {
    this.showById = function (id) {
        document.getElementById(id).style.display = 'block';
    };

    this.hideById = function (id) {
        document.getElementById(id).style.display = 'none';
    };

    this.existsById = function (id) {
        var elem = document.getElementById(id);
        return this.existsByElement(elem);
    };

    this.existsByClassName = function (className) {
        var elems = document.getElementsByClassName(className);
        return elems.length > 0;
    };

    this.existsByElement = function (elem) {
        return typeof elem !== 'undefined' && elem !== null;
    };

    this.hasClassByElement = function (elem, className) {
        return elem.classList.contains(className);
    };
})();;
; window.ePlus.modules.listViewPrcRow = (function () {
    var res = window.ePlus.resources;
    var util = window.ePlus.util;
    var listView = window.ePlus.modules.listView;

    var removeLineItemFromUserRequestOrder = function (lineItemRequestType, userRequestOrderLineItemId) {
        var lineItem = getLineItem(userRequestOrderLineItemId);
        window.ePlus.modules.prc.undoPrcRequest(lineItemRequestType, lineItem, reloadCurrentPage);
    }

    var saveUserRequestOrderLineItem = function (lineItemRequestType, userRequestOrderLineItemId, callback) {
        var lineItem = getLineItem(userRequestOrderLineItemId);
        var newApprovedQty = document.getElementById("approvedQty" + userRequestOrderLineItemId).value;

        if (lineItem.RequestedQty < newApprovedQty && !confirm(res.getRes("you_approved_more_than_requested_are_you_sure"))) {
            return;
        }

        lineItem.ApprovedQty = newApprovedQty;

        window.ePlus.modules.prc.savePrcRequest("PUT", lineItemRequestType, lineItem, callback);
    }

    function getLineItem(userRequestOrderLineItemId) {
        var lineItem = $('#as_' + userRequestOrderLineItemId).data('user-request-order-line-item');

        if (!lineItem.ApprovedQty) {
            lineItem.ApprovedQty = lineItem.RequestedQty;
        }

        return lineItem;
    }

    var buildLineItemsFromListView = function (listViewLineItems, lineItemStatus, approvedQty) {
        lineItems = [];

        _.forEach(listViewLineItems, function (item, i) {
            var lineItem = item;
            lineItem.Status = lineItemStatus;
            lineItem.ApprovedQty = approvedQty;
            lineItems.push(lineItem);
        });
    }

    var saveNewLineItemQuantityValue = function (lineItemRequestType, userRequestOrderLineItemId) {
        var saveInput = document.getElementById("approvedQty" + userRequestOrderLineItemId);
        var oldQuantity = saveInput.dataset.originalQuantity;
        var newApprovedQty = saveInput.value;
        if (oldQuantity !== newApprovedQty) {
            var requestQty = saveInput.dataset.requestedQuantity;
            saveInput.classList.toggle("unSavedUnits");
            saveUserRequestOrderLineItem(lineItemRequestType, userRequestOrderLineItemId, function() {
                saveInput.classList.toggle("unSavedUnits");
                var qtyMessage = document.getElementById("qty-message-" + userRequestOrderLineItemId);
                if (requestQty !== newApprovedQty) {
                    qtyMessage.style.display = "initial";
                    $("#qty-message-" + userRequestOrderLineItemId).webuiPopover({
                        trigger: "click",
                        cache: false,
                        content: "<span class='bold'>" + res.getRes("requested_quantity") + ": </span>" + requestQty,
                        placement: 'bottom',
                        container: "#pageContent"
                    });
                } else {
                    qtyMessage.style.display = "none";
                }
            });
        }
    }

    var setLineItemStatus = function (lineItemRequestType, userRequestOrderLineItemId, status) {
        var lineItem = getLineItem(userRequestOrderLineItemId);

        lineItem.status = status;

        ePlus.modules.prc.savePrcRequest("PUT", lineItemRequestType, lineItem, function () {
            removeListViewItemsAndUpdateListView([userRequestOrderLineItemId]);
            $("#as_" + userRequestOrderLineItemId).slideUp();

            updateDashboardValue();
        });
    }

    var checkLineItemKey = function (elem, event) {
        if (!event) return false;
        var $e = $(elem);

        switch (event.which) {
        case 13: // ENTER
            $e.blur();
            return false;
        }
        return false;
    }

    var enableUserRequestOrderApprovedQuantity = function (userRequestOrderLineItemId) {
        var approvedQuantityElement = document.getElementById("approvedQty" + userRequestOrderLineItemId)
        var editElement = document.getElementById("editUserRequestOrder" + userRequestOrderLineItemId);
        var saveElement = document.getElementById("saveUserRequestOrder" + userRequestOrderLineItemId);
        var cancelElement = document.getElementById("cancelEditUserRequestOrder" + userRequestOrderLineItemId);
        var undoElement = document.getElementById("undoUserRequestOrder" + userRequestOrderLineItemId);

        approvedQuantityElement.disabled = false;
        editElement.classList.add("hidden");
        saveElement.classList.remove("hidden");
        cancelElement.classList.remove("hidden");
        undoElement.classList.add("hidden");
    }

    var disableUserRequestOrderApprovedQuantity = function (userRequestOrderLineItemId, doResetApprovedQty) {
        var approvedQuantityElement = document.getElementById("approvedQty" + userRequestOrderLineItemId)
        var editElement = document.getElementById("editUserRequestOrder" + userRequestOrderLineItemId);
        var saveElement = document.getElementById("saveUserRequestOrder" + userRequestOrderLineItemId);
        var cancelElement = document.getElementById("cancelEditUserRequestOrder" + userRequestOrderLineItemId);
        var undoElement = document.getElementById("undoUserRequestOrder" + userRequestOrderLineItemId);

        approvedQuantityElement.disabled = true;
        editElement.classList.remove("hidden");
        saveElement.classList.add("hidden");
        cancelElement.classList.add("hidden");
        undoElement.classList.remove("hidden");

        if (doResetApprovedQty) {
            approvedQuantityElement.value = approvedQuantityElement.getAttribute("data-original-quantity");
        }
    }

    var sendUserRequestOrderLineItem = function (orderType, status, lineItemStatus, lineItem, distributor, orgId, userId, callback) {
        var today = new Date();

        var userRequestOrder = {
            userRequestOrderId: 0,
            name: today.toISOString(),
            type: orderType,
            status: status,
            distributor: distributor,
            auditOrgId: orgId,
            auditUserId: userId
        };

        var onSuccess = function (newUserRequestOrder) {
            lineItem.UserRequestOrderId = newUserRequestOrder.userRequestOrderId;
            lineItem.status = lineItemStatus;            

            window.ePlus.modules.prc.savePrcRequest("PATCH", null, [lineItem],
                function () {
                    if (listView.getListViewProperty("itemType") === util.getEnumValue("itemType", "USERREQUESTORDERLINEITEM")) {
                        var userRequestOrderLineItemId = lineItem.UserRequestOrderLineItemId;
                        removeListViewItemsAndUpdateListView([userRequestOrderLineItemId]);
                        $("#as_" + userRequestOrderLineItemId).slideUp();

                        updateDashboardValue();
                    }
                    
                    if (typeof callback === "function") {
                        callback();
                    }
                });
        };

        ePlus.modules.prc.saveUserRequestOrder("POST", userRequestOrder, onSuccess);
    }

    var sendUserRequestOrderLineItemById = function (orderType, status, lineItemStatus, lineItemId, orgId, userId) {
        var lineItem = getLineItem(lineItemId);

        lineItem.attributes = [{
            userRequestOrderLineItemId: lineItem.UserRequestOrderLineItemId,
            type: 4,
            value: lineItem.Shipping.MethodDescription,
            auditOrgId: orgId,
            auditUserId: userId
        }];

        var distributor = {
            id: lineItem.DistributorId
        };

        sendUserRequestOrderLineItem(orderType, status, lineItemStatus, lineItem, distributor, orgId, userId);
    }

    var sendSelectedUserRequestOrderLineItems = function (orderType, status, lineItemStatus, orgId, userId) {
        var lineItems = buildLineItem(lineItemStatus);

        if (_.isNil(lineItems) || lineItems.length === 0) {
            alert(res.getRes("select_at_least_one_title"));
            return;
        }

        var today = new Date();
        var userRequestOrderLineItemId = lineItems[0].userRequestOrderLineItemId;
        var distributor = {
            id: lineItems[0].DistributorId
        };

        var userRequestOrder = {
            userRequestOrderId: 0,
            name: today.toISOString(),
            type: orderType,
            status: status,
            distributor: distributor,
            auditOrgId: orgId,
            auditUserId: userId
        };

        var onSuccess = function (newUserRequestOrder) {
            _.forEach(lineItems, function (item, i) {
                item.UserRequestOrderId = newUserRequestOrder.userRequestOrderId;
                item.attributes = [{
                    userRequestOrderLineItemId: item.UserRequestOrderLineItemId,
                    type: 4,
                    value: item.Shipping.MethodDescription,
                    auditOrgId: orgId,
                    auditUserId: userId
                }];
            });

            window.ePlus.modules.prc.savePrcRequest("PATCH", null, lineItems,
                function () {
                    var userRequestOrderLineItemIds = _.map(lineItems, function(li) {
                        return li.UserRequestOrderLineItemId;
                    });
                    removeListViewItemsAndUpdateListView(userRequestOrderLineItemIds);
                    _.forEach(userRequestOrderLineItemIds, function (id) {
                        $("#as_" + id).slideUp();
                    });

                    updateDashboardValue();
                    closeModal();
                });
        };

        ePlus.modules.prc.saveUserRequestOrder("POST", userRequestOrder, onSuccess);
    }

    function buildLineItem(lineItemStatus) {
        var userRequestOrderLineItemIds = window.getSelectedItems();
        var lineItems = [];

        _.forEach(userRequestOrderLineItemIds, function (id, i) {
            var lineItem = getLineItem(id);

            if (!lineItem.ApprovedQty) {
                lineItem.ApprovedQty = lineItem.RequestedQty;
            }
            lineItem.Status = lineItemStatus;
            lineItems.push(lineItem);
        });

        return lineItems;
    }

    function updateDashboardValue() {
        GetDashboardValuePlusRefresh(util.getEnumValue("resultType", "PRCSOPEN"), util.getEnumValue("dashType", "DASHDRC"));
        GetDashboardValuePlusRefresh(util.getEnumValue("resultType", "PRCSUNSENT"), util.getEnumValue("dashType", "DASHDRC"));
        GetDashboardValuePlusRefresh(util.getEnumValue("resultType", "PRCSSENT"), util.getEnumValue("dashType", "DASHDRC"));
        GetDashboardValuePlusRefresh(util.getEnumValue("resultType", "PRCSDECLINED"), util.getEnumValue("dashType", "DASHDRC"));
    }

    return {
        removeLineItemFromUserRequestOrder: removeLineItemFromUserRequestOrder,
        saveUserRequestOrderLineItem: saveUserRequestOrderLineItem,
        sendUserRequestOrderLineItemById: sendUserRequestOrderLineItemById,
        enableUserRequestOrderApprovedQuantity: enableUserRequestOrderApprovedQuantity,
        disableUserRequestOrderApprovedQuantity: disableUserRequestOrderApprovedQuantity,
        sendSelectedUserRequestOrderLineItems: sendSelectedUserRequestOrderLineItems,
        sendUserRequestOrderLineItem: sendUserRequestOrderLineItem,
        setLineItemStatus: setLineItemStatus
    }
})();;
;
ePlus.modules.deleteUser = (function () {
    var res = window.ePlus.resources;
    var openDeleteUser = function() {
        var url = "/GetTreelineControl.aspx?controlName=/uc/user/DeleteUser.ascx";
        $("#userContentArea").load(url);
    }

    var returnToUserProfile = function(openTo) {
        var url = "/GetTreelineControl.aspx?controlName=/uc/user/admin/userProfileFrame.ascx&openTo=" + openTo;
        $("#popModal_inner").load(url);
    }

    var deleteUser = function(appUserId) {
        var deleteUserText = $("#deleteUser").val();

        if (_.isNil(deleteUserText) || deleteUserText.toLowerCase() !== "delete") {
            alert(res.getRes("type_delete_to_confirm_user_deletion"));
            return;
        }

        ePlus.user.deleteUser(appUserId);
    }

    return {
        openDeleteUser: openDeleteUser,
        returnToUserProfile: returnToUserProfile,
        deleteUser: deleteUser
    }
})();;
;
window.ePlus.modules.suggestions = (function () {
    var SuggestionSummary = function(change) {
        this.currUnitCount = +$('#orderQty').html();
        this.currTitleCount = +$('#orderedTitles').html();
        this.currTotalValue = +$('#orderCurrencyValue').val();
        this.skuPrice = window.prices[change.sku] || 0;
        this.newUnits = Math.max(change.newUnits, 0) || 0;
        this.originalUnits = Math.max(change.originalUnits, 0) || 0;
        this.unitDiff = this.newUnits - this.originalUnits;
        this.sku = change.sku;
        this.storeId = change.storeId;
    }

    SuggestionSummary.prototype.isAlreadySuggested = function() {
        var alreadySuggested = false;
        var self = this;
        $('.inputOrder_' + this.sku, $('#as_' + this.sku)).each(function() {
            var $e = $(this);
            var elemStoreId = $e.data("storeid");
            if (self.storeId !== elemStoreId && +$e.val() > 0) {
                alreadySuggested = true;
            }
        });

        return alreadySuggested;
    }

    SuggestionSummary.prototype.getTitleCount = function() {
        var titleDiff = 0;
        var isOnlySkuSuggestion = !this.isAlreadySuggested();
        if (isOnlySkuSuggestion && this.originalUnits === 0 && this.newUnits > 0) {
            titleDiff = 1;
        } else if (isOnlySkuSuggestion && this.originalUnits > 0 && this.newUnits === 0) {
            titleDiff = -1;
        }

        return this.currTitleCount + titleDiff;
    }

    SuggestionSummary.prototype.getUnitCount = function() {
        return this.currUnitCount + this.unitDiff;
    }

    SuggestionSummary.prototype.getSuggestionTotal = function() {
        return this.currTotalValue + (this.unitDiff * this.skuPrice);
    };

    var totalContainerDefaultState = '--';

    var getSuggestionTotalsFromApi = function () {
        var url = '/api/orderSuggestionSummary';

        if (window.catalogID) {
            url += '?catalogID=' + window.catalogID;
        }

        if (window.rows && window.rows.length > 0) {
            $.ajax({
                type: 'GET',
                url: url
            }).done(function (data) {
                window.updateOrderSummary(data);
            }).fail(function () {
                console.warn('Failed to load order summary.');
            });
        }
    }

    var doesSuggestionTotalExist = function () {
        return $('#orderedTitles').length > 0 && $('#orderedTitles').html() !== totalContainerDefaultState;
    };

    var getSuggestionSummaryFromChange = function(suggestionChange) {
        var summary = new SuggestionSummary(suggestionChange);

        return {
            orderedTitles: summary.getTitleCount(),
            orderedUnits: summary.getUnitCount(),
            orderedTotalValue: summary.getSuggestionTotal()
        };
    }

    var isChangeValidAndSuggestionsAreInitialized = function(change) {
        return change && change.sku && doesSuggestionTotalExist();
    }

    var getSuggestionTotals = function(suggestionChange) {
        if (isChangeValidAndSuggestionsAreInitialized(suggestionChange)) {
            var summary = getSuggestionSummaryFromChange(suggestionChange);
            window.updateOrderSummary(summary);
        } else {
            getSuggestionTotalsFromApi();
        }
    };

    return {
        getSuggestionTotals: getSuggestionTotals
    }
})();;
; ePlus.modules.markups = (function () {
    var createMarkupNote = function (mailingId, sku, noteText) {
        return $.ajax({
            url: 'api/me/markups/' + mailingId + '/products/' + sku + '/notes/0',
            type: 'POST',
            dataType: 'json',
            contentType: 'application/json',
            data: JSON.stringify(noteText)
        });
    };

    var updateMarkupNote = function (mailingId, sku, noteText) {
        return $.ajax({
            url: 'api/me/markups/' + mailingId + '/products/' + sku + '/notes/0',
            type: 'PUT',
            dataType: 'json',
            contentType: 'application/json',
            data: JSON.stringify(noteText)
        });
    };

    var deleteMarkupNote = function (mailingId, sku) {
        return $.ajax({
            url: 'api/me/markups/' + mailingId + '/products/' + sku + '/notes/0',
            type: 'DELETE'
        });
    };

    var saveMarkupPriorities = function (priorities) {
        return $.ajax({
            type: 'POST',
            url: 'api/me/markups/priorities',
            contentType: 'application/json',
            data: JSON.stringify(priorities)
        });
    };

    var failureHandler = function (jqXHR) {
        var errorMessages = [];

        if (jqXHR && jqXHR.responseText) {
            var result = JSON.parse(jqXHR.responseText);

            if (result.errors && result.errors.length > 0) {
                errorMessages = errorMessages.concat(getLocalizedErrors(result.errors));
            }

            if (result.validationErrors) {
                errorMessages = errorMessages.concat(getLocalizedValidationErrors(result.validationErrors));
            }
        } else {
            errorMessages.push(getRes('error_unexpected'));
        }

        alert(errorMessages.join('\r\n'));
    };

    var getLocalizedErrors = function (errors) {
        return errors.map(function (e) { return getRes(e); });
    };

    var getLocalizedValidationErrors = function (validationErrors) {
        var errors = [].concat.apply([], Object.keys(validationErrors).map(function (key) { return validationErrors[key]; }));
        return getLocalizedErrors(errors);
    };

    return {
        createMarkupNote: createMarkupNote,
        updateMarkupNote: updateMarkupNote,
        deleteMarkupNote: deleteMarkupNote,
        saveMarkupPriorities: saveMarkupPriorities,
        failureHandler: failureHandler
    };
})();;
;
window.ePlus.modules.imprintGroup = (function () {
    var createImprintGroup = function (newImprintGroup, callback, alwaysCallback) {
        if (!newImprintGroup) return;

        $.ajax({
            type: 'POST',
            dataType: 'json',
            contentType: 'application/json; charset=utf-8',
            data: JSON.stringify(newImprintGroup),
            url: 'api/organization/' + newImprintGroup.orgId + '/imprintGroups'
        })
            .done(callback)
            .fail(window.ePlus.util.ajaxHelper.handleGenericAjaxFail)
            .always(alwaysCallback);
    };

    var updateImprintGroup = function (existingImprintGroup, callback, alwaysCallback) {
        if (!existingImprintGroup || !existingImprintGroup.id || !existingImprintGroup.orgId) return;
        $.ajax({
            type: 'PUT',
            dataType: 'json',
            contentType: 'application/json; charset=utf-8',
            data: JSON.stringify(existingImprintGroup),
            url: 'api/organization/' + existingImprintGroup.orgId + '/imprintGroups/' + existingImprintGroup.id
        })
            .done(callback)
            .fail(window.ePlus.util.ajaxHelper.handleGenericAjaxFail)
            .always(alwaysCallback);
    };

    var deleteImprintGroup = function (groupId, orgId, callback) {
        if (!groupId || !orgId) return;

        $.ajax({
            type: 'DELETE',
            dataType: 'json',
            contentType: 'application/json; charset=utf-8',
            url: 'api/organization/' + orgId + '/imprintGroups/' + groupId
        })
            .done(callback)
            .fail(window.ePlus.util.ajaxHelper.handleGenericAjaxFail);
    };

    return {
        createImprintGroup: createImprintGroup,
        updateImprintGroup: updateImprintGroup,
        deleteImprintGroup: deleteImprintGroup
    };
})();
;
window.ePlus.modules.GenericFormHandler = function ($form, onSubmit, onSubmitCallback) {
    this.$form = $form;
    this.onSubmit = onSubmit;
    this.onSubmitCallback = onSubmitCallback;
    this.formStatus = { isSaving: false };
}

window.ePlus.modules.GenericFormHandler.prototype = {
    formSubmitCleanup: function () {
        this.formStatus.isSaving = false;
    },
    getFormData: function () {
        var formData = this.$form.serializeJSON({
            parseNumbers: true,
            parseBooleans: true
        });

        return formData;
    },
    isValidFormData: function () {
        if (!this.$form[0].checkValidity()) {
            this.$form.addClass('was-validated');
            return false;
        }

        return true;
    },
    initFormEvents: function () {
        this.formStatus.isSaving = false;

        var self = this;
        this.$form.on('submit', function (e) {
            if (self.formStatus.isSaving) {
                return false;
            }

            self.formStatus.isSaving = true;

            if (self.isValidFormData()) {
                self.onSubmit(self.getFormData(), self.onSubmitCallback, self.formSubmitCleanup.bind(self));
            }
            e.preventDefault();
            e.stopPropagation();
            return false;
        });
    }
};
;
window.ePlus.modules.manageImprintGroup = (function () {
    var formId = 'create-edit-imprint-group';
    var onSubmitCallback = function() {
        window.closeModal();
        window.reloadList();
    }
    var initCreateForm = function () {
        var onSubmit = ePlus.modules.imprintGroup.createImprintGroup;
        var formHandler = new ePlus.modules.GenericFormHandler($('#' + formId), onSubmit, onSubmitCallback);
        formHandler.initFormEvents();
    };

    var initEditForm = function () {
        var onSubmit = ePlus.modules.imprintGroup.updateImprintGroup;
        var formHandler = new ePlus.modules.GenericFormHandler($('#' + formId), onSubmit, onSubmitCallback);
        formHandler.initFormEvents();
    };

    return {
        initCreateForm: initCreateForm,
        initEditForm: initEditForm
    }
})();;
;
window.ePlus.modules.listViewImprintGroupGridRow = (function () {
    var res = window.ePlus.resources;

    var initImprintGroupEdit = function () {
        $('.edit-imprint-group', '#itemContainer').each(function () {
            var imprintGroupId = $(this).data('imprint-group-id');
            $(this).off().on('click', function () {
                openManageImprintGroup(imprintGroupId);
            });
        });
    }

    var initImprintGroupDelete = function () {
        $('.delete-imprint-group', '#itemContainer').each(function () {
            var imprintGroupId = $(this).data('imprint-group-id');
            var imprintOrgId = $(this).data('imprint-org-id');
            $(this).off().on('click', function () {
                if (confirm(res.getRes("confirm_delete_imprint_group"))) {
                    window.ePlus.modules.imprintGroup.deleteImprintGroup(imprintGroupId, imprintOrgId, window.reloadList);
                }
            });
        });
    }

    var initActions = function () {
        initImprintGroupEdit();
        initImprintGroupDelete();
    }

    return {
        initActions: initActions
    }
})();;
;
window.ePlus.modules.listView.prc = (function () {
    var res = window.ePlus.resources;
    var util = window.ePlus.util;
    var listView = window.ePlus.modules.listView;

    var getSelectedPrcProfileId = function () {
        return listView.getListViewProperty('selectedPrcProfileId');
    };

    var setSelectedPrcProfileId = function (profileId) {
        listView.setListViewProperty('selectedPrcProfileId', profileId);
    };
    
    var initialize = function () {
        var resultType = listView.getListViewProperty('resultType');

        if (resultType === util.getEnumValue('resultType', 'PRCSADMIN')) {
            initializePrcAdminListView();
        } else {
            initializePrcRequestListView();
        }
    };

    var initializePrcAdminListView = function () {
        initializeDistributorPopOver();
        initializePrcTitleStatusCheckboxes();
    };

    var initializePrcRequestListView = function () {
        initializeProfileContentExpander();
        initializeOriginalQuantityPreviews();
        initializeRequestMessageExpander();
        initializeDistributorSelector();
        initializeShippingAddressPopOver();
        initializeDatePickers();
    };

    var initializeDistributorPopOver = function () {
        $('.prc-distributors', '#itemContainer').webuiPopover({
            type: 'async',
            trigger: 'hover',
            placement: 'left',
            cache: true
        });
    };

    var initializePrcTitleStatusCheckboxes = function () {
        $('.prc-title-status', '#itemContainer').on('click', function () {
            updatePrcTitleStatus(this);
        });
    };

    var initializeProfileContentExpander = function () {
        var moreText = res.getRes("more");
        var lessText = res.getRes("less");
        var charLimit = 150;

        $(document)
            .off('click', '.more-link')
            .on('click', '.more-link', function () {
                var $this = $(this);
                if ($this.hasClass('less')) {
                    $this.removeClass('less');
                    $this.html(moreText);
                } else {
                    $this.addClass('less');
                    $this.html(lessText);
                }
                $this.parent().prev().toggle();
                $this.prev().toggle();
                return false;
            });

        $(".more").each(function () {
            var $this = $(this);
            $this.removeClass("dotDot");
            if ($this.hasClass("shortened")) return;

            $this.addClass("shortened");
            var content = $this.html();
            if (content.length > charLimit) {
                var c = content.substr(0, charLimit);
                var h = content.substr(charLimit, content.length - charLimit);
                var html = c + '<span class="more-ellipses"></span> <span class="more-content"><span>' + h + '</span> <a href="#" class="more-link ePlusLink clickable">' + moreText + '</a></span>';
                $this.html(html);
                $(".more-content span").hide();
            }
        });
    };

    var initializeOriginalQuantityPreviews = function () {
        for (var i = 0; window.items && i < window.items.length; i++) {
            var $input = $("#approvedQty" + window.items[i]);
            var currentValue = $input.val();
            var requestValue = $input.attr("data-requested-quantity");
            if (currentValue !== requestValue) {
                $("#qty-message-" + window.items[i]).show().webuiPopover({
                    trigger: "click",
                    cache: false,
                    content: "<span class='bold'>" + res.getRes("requested_quantity") + ": </span>" + requestValue,
                    placement: 'bottom-right',
                    container: "#pageContent"
                });
            }
        }
    };

    var initializeRequestMessageExpander = function () {
        $('.req-purpose-icon', $('#itemContainer')).click(function () {
            var $icon = $(this);
            $icon.toggleClass("icon-drop-up-icon-01 icon-drop-down-icon");
            var requestId = $icon.data('request-id');
            $('#req-message-' + requestId).toggleClass('hidden');
            $('#req-message-full-' + requestId).toggleClass('hidden');
        });
    };

    var initializeDistributorSelector = function () {
        initializeDistributorIdSelector();
        initializeDistributorShippingCodeSelector();

        $('select.distributor-id', '#itemContainer').trigger('change');
    };

    var initializeShippingAddressPopOver = function () {
        $('.ship-to', '#itemContainer').webuiPopover({
            type: 'async',
            trigger: 'hover',
            delay: 300
        });
    };

    var initializeDatePickers = function () {
        // Within datepicker, the en-US culture is stored as empty string, not en-US.
        var userCulture = window.ePlus.user.culture === 'en-US' ? '' : window.ePlus.user.culture;
        var culture = $.datepicker.regional[userCulture];
        var options = $.extend(
            {},
            culture,
            {
                onSelect: function () {
                    var $this = $(this);
                    var $parent = $this.parent();
                    var userRequestOrderLineItemId = $this.data('user-request-order-line-item-id');
                    var date = $this.val();
                    var callback = function () {
                        // Once the ship date has been updated, hide the datepicker and just show the date in plain text.
                        $this.hide();
                        $parent.append(date);
                    };

                    // Disable the datepicker to indicate the page is loading.
                    $this.prop('disabled', true);

                    ePlus.modules.prc.updateUserRequestOrderLineItemShipDate(userRequestOrderLineItemId, date, callback);
                }
            }
        );

        $('.set-ship-date', '#itemContainer').each(function () {
            $(this).datepicker(options);
        });
    };

    var initializeDistributorIdSelector = function () {
        $('select.distributor-id', '#itemContainer')
            .on('change', function () {
                var $prcRow = getParentPrcRow(this);
                var userRequestOrderLineItem = getPrcRowUserRequestOrderLineItem($prcRow);

                userRequestOrderLineItem.DistributorId = parseInt($(this).val());
                userRequestOrderLineItem.ShippingCode = null;

                setPrcRowUserRequestOrderLineItem($prcRow, userRequestOrderLineItem);
                updateDistributorShippingOptions($prcRow, userRequestOrderLineItem, function () {
                    $('select.distributor-shipping-code', $prcRow).trigger('change');
                });
            });
    };

    var initializeDistributorShippingCodeSelector = function () {
        $('select.distributor-shipping-code', '#itemContainer')
            .on('change', function () {
                var $prcRow = getParentPrcRow(this);
                var userRequestOrderLineItem = getPrcRowUserRequestOrderLineItem($prcRow);

                userRequestOrderLineItem.ShippingCode = $(this).val();
                userRequestOrderLineItem.Shipping.MethodDescription = $(this).children('option:selected').html();

                setPrcRowUserRequestOrderLineItem($prcRow, userRequestOrderLineItem);
            });
    };

    var updateDistributorShippingOptions = function ($prcRow, userRequestOrderLineItem, callback) {
        var profileId = getSelectedPrcProfileId();
        var $select = $prcRow.find('select.distributor-shipping-code');

        removeDistributorShippingOptions($select)

        ePlus.modules.prc.getDistributorShippingOptions(profileId, userRequestOrderLineItem.DistributorId, userRequestOrderLineItem.Address)
            .done(function (shippingOptions) {
                addDistributorShippingOptions($select, shippingOptions);
            })
            .fail(function () {
                alert(res.getRes('error_unexpected'));
            })
            .always(callback);
    };

    var removeDistributorShippingOptions = function ($select) {
        $select.children().remove();
    };

    var addDistributorShippingOptions = function ($select, shippingOptions) {
        if (shippingOptions && shippingOptions.length > 0) {
            for (var i = 0, max = shippingOptions.length; i < max; i++) {
                var shippingOption = shippingOptions[i];
                var $option = $('<option />', {
                    text: shippingOption.shippingDescription,
                    value: shippingOption.shippingCode
                });

                $select.append($option);
            }
        }
    };

    var getPrcRowUserRequestOrderLineItem = function ($prcRow) {
        return $prcRow.data('user-request-order-line-item');
    };

    var setPrcRowUserRequestOrderLineItem = function ($prcRow, userRequestOrderLineItem) {
        $prcRow.data('user-request-order-line-item', userRequestOrderLineItem);
    };

    var getParentPrcRow = function (elem) {
        return $(elem).closest('.prc-row');
    };

    var updatePrcTitleStatus = function (elem) {
        var $row = $(elem).closest('tr');
        var prcTitle = $row.data('prc-title');
        var activeDistributors = $row.data('active-distributors');
        
        prcTitle.status = $(elem).is(':checked') ? 1 : 0;

        if (activeDistributors > 0) {
            var message = getMessage(prcTitle.status);

            if (!confirm(message)) {
                $(elem).prop('checked', !prcTitle.status);
                return;
            }
        }

        ePlus.modules.prc.updatePrcTitle(prcTitle)
            .done(function () {
                reloadCurrentPage();
            })
            .fail(function () {
                alert(res.getRes('error_unexpected'));
            });
    };

    var getMessage = function (prcTitleStatus) {
        if (prcTitleStatus === 1) {
            return res.getRes('are_you_sure_you_want_to_make_title_printable');
        } else {
            return res.getRes('are_you_sure_you_want_to_remove_title_printable');
        }
    };
        
    return {
        getSelectedPrcProfileId: getSelectedPrcProfileId,
        setSelectedPrcProfileId: setSelectedPrcProfileId,
        initialize: initialize
    };
})();;
'use strict';

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var TestClass1 = function () {
    function TestClass1() {
        _classCallCheck(this, TestClass1);
    }

    _createClass(TestClass1, [{
        key: 'someMethod',
        value: function someMethod() {
            console.log('first method');
        }
    }]);

    return TestClass1;
}();

var someFun = function someFun() {
    for (var _len = arguments.length, someArgs = Array(_len), _key = 0; _key < _len; _key++) {
        someArgs[_key] = arguments[_key];
    }

    console.log(someArgs);
    console.log('somefun');
};
var _apple = { apple: 4 },
    apple = _apple.apple;
var _hotdog = { hotdog: 4 },
    hotdog = _hotdog.hotdog;


if (!window.ePlus.modules) {
    window.ePlus.modules = {};
}

if (!window.ePlus.modules.newsletter) {
    window.ePlus.modules.newsletter = {};
}

ePlus.modules.newsletter.api = function () {
    function subscribeNewsUsersToNewsletters(newUserMailingListDto) {
        var apiUrl = '/api/mailingLists/user/signup';
        var json = JSON.stringify(newUserMailingListDto);

        return $.ajax({
            url: apiUrl,
            data: json,
            type: 'POST',
            contentType: 'application/json; charset=utf-8',
            dataType: 'json'
        });
    }

    return {
        subscribeNewsUsersToNewsletters: subscribeNewsUsersToNewsletters
    };
}();

var CommunityShareCatalogInclusion = function () {
    function CommunityShareCatalogInclusion() {
        _classCallCheck(this, CommunityShareCatalogInclusion);
    }

    _createClass(CommunityShareCatalogInclusion, [{
        key: 'deleteAffiliationShareCatalogInclusions',
        value: function deleteAffiliationShareCatalogInclusions(catalogId, appUserId, affiliationShareCatalogInclusions) {
            var url = 'api/catalogs/' + catalogId + '/users/' + appUserId + '/communities/shares-inclusions/affiliations';
            return $.ajax({
                type: 'DELETE',
                url: url,
                contentType: 'application/json',
                data: JSON.stringify(affiliationShareCatalogInclusions)
            });
        }
    }, {
        key: 'updateCommunityShareCatalogInclusions',
        value: function updateCommunityShareCatalogInclusions(type, catalogId, appUserId, communityShareCatalogInclusions) {
            var url = '/api/catalogs/' + catalogId + '/users/' + appUserId + '/communities/shares-inclusions';
            var data = {
                CommunityShareTypes: communityShareCatalogInclusions
            };

            return $.ajax({
                type: type,
                url: url,
                contentType: 'application/json',
                data: JSON.stringify(data)
            });
        }
    }, {
        key: 'saveAffiliationShareCatalogInclusions',
        value: function saveAffiliationShareCatalogInclusions(catalogId, appUserId, affiliationShareCatalogInclusions) {
            var communityShareType = [{
                CommunityType: 4
            }];

            return this.deleteCommunityShareCatalogInclusion(catalogId, appUserId, communityShareType).done(this.updateAffiliationShareCatalogInclusions(catalogId, appUserId, affiliationShareCatalogInclusions));
        }
    }, {
        key: 'deleteCommunityShareCatalogInclusion',
        value: function deleteCommunityShareCatalogInclusion(catalogId, appUserId, communityShareType) {
            var url = '/api/catalogs/' + catalogId + '/users/' + appUserId + '/communities/shares-inclusions';
            return $.ajax({
                type: 'DELETE',
                url: url,
                contentType: 'application/json',
                data: JSON.stringify(communityShareType)
            });
        }
    }, {
        key: 'updateAffiliationShareCatalogInclusions',
        value: function updateAffiliationShareCatalogInclusions(catalogId, appUserId, affiliationShareCatalogInclusions) {
            var url = 'api/catalogs/' + catalogId + '/users/' + appUserId + '/communities/shares-inclusions/affiliations';
            var data = {
                AffiliationShareTypes: affiliationShareCatalogInclusions
            };

            return $.ajax({
                type: 'POST',
                url: url,
                contentType: 'application/json',
                data: JSON.stringify(data)
            });
        }
    }, {
        key: 'getCommunityColleagueJqueryElement',
        value: function getCommunityColleagueJqueryElement() {
            var COLLEAGUE_COMMUNITY_ELEMENT_ID = 'community-type-2';
            return $('#' + COLLEAGUE_COMMUNITY_ELEMENT_ID);
        }
    }, {
        key: 'doUpdateColleagueShare',
        value: function doUpdateColleagueShare() {
            var CATALOG_COLLECTIONS_ENUM = 'CATALOGCOLLECTIONS';
            var TITLE_CATALOG_ENUM = 'TITLE_CATALOG';

            return ePlus.modules.listView.isResultType(TITLE_CATALOG_ENUM) || ePlus.modules.listView.isResultType(CATALOG_COLLECTIONS_ENUM) && this.getCommunityColleagueJqueryElement().length;
        }
    }, {
        key: 'updateColleagueShare',
        value: function updateColleagueShare(catalogId) {
            var doShareWithColleagues = this.getCommunityColleagueJqueryElement().hasClass('box_checked');
            $.ajax({
                type: 'POST',
                url: '/api/catalogs/' + catalogId + '/shares/' + doShareWithColleagues + '/organizations',
                contentType: 'application/json'
            });
        }
    }]);

    return CommunityShareCatalogInclusion;
}();

if (typeof ePlus.modules.community === 'undefined') {
    ePlus.modules.community = {};

    if (typeof ePlus.modules.community.api === 'undefined') {
        ePlus.modules.community.api = {};
    }
}

ePlus.modules.community.api.catalogInclusion = new CommunityShareCatalogInclusion();

var CommunityShareException = function () {
    function CommunityShareException() {
        _classCallCheck(this, CommunityShareException);
    }

    _createClass(CommunityShareException, [{
        key: 'deleteAffiliationShareExceptions',
        value: function deleteAffiliationShareExceptions(sku, appUserId, affiliationShareExceptions) {
            var url = 'api/products/' + sku + '/users/' + appUserId + '/communities/shares-exceptions/affiliations';
            return $.ajax({
                type: 'DELETE',
                url: url,
                contentType: 'application/json',
                data: JSON.stringify(affiliationShareExceptions)
            });
        }
    }, {
        key: 'deleteCommunityShareExceptions',
        value: function deleteCommunityShareExceptions(sku, appUserId, communityShareType) {
            var url = '/api/products/' + sku + '/users/' + appUserId + '/communities/shares-exceptions';
            return $.ajax({
                type: 'DELETE',
                url: url,
                contentType: 'application/json',
                data: JSON.stringify(communityShareType)
            });
        }
    }, {
        key: 'getCommunityReviewShareCount',
        value: function getCommunityReviewShareCount(sku) {
            var url = 'api/me/reviews/' + sku + '/shares';

            return $.ajax({
                type: 'GET',
                url: url
            });
        }
    }, {
        key: 'saveAffiliationShareExceptions',
        value: function saveAffiliationShareExceptions(sku, appUserId, saveAsDefault, affiliationShareExceptions) {
            var communityShareType = [{
                CommunityType: 4
            }];

            return this.deleteCommunityShareExceptions(sku, appUserId, communityShareType).done(this.updateAffiliationShareExceptions(sku, appUserId, saveAsDefault, affiliationShareExceptions));
        }
    }, {
        key: 'updateAffiliationShareExceptions',
        value: function updateAffiliationShareExceptions(sku, appUserId, saveAsDefault, affiliationShareExceptions) {
            var url = 'api/products/' + sku + '/users/' + appUserId + '/communities/shares-exceptions/affiliations';
            var data = {
                AffiliationShareTypes: affiliationShareExceptions,
                SaveAsDefault: saveAsDefault
            };

            return $.ajax({
                type: 'POST',
                url: url,
                contentType: 'application/json',
                data: JSON.stringify(data)
            });
        }
    }, {
        key: 'updateCommunityShareExceptions',
        value: function updateCommunityShareExceptions(type, sku, appUserId, saveAsDefault, communityShareExceptions) {
            var url = '/api/products/' + sku + '/users/' + appUserId + '/communities/shares-exceptions';
            var data = {
                CommunityShareTypes: communityShareExceptions,
                SaveAsDefault: saveAsDefault
            };

            return $.ajax({
                type: type,
                url: url,
                contentType: 'application/json',
                data: JSON.stringify(data)
            });
        }
    }]);

    return CommunityShareException;
}();

if (typeof ePlus.modules.community === 'undefined') {
    ePlus.modules.community = {};

    if (typeof ePlus.modules.community.api === 'undefined') {
        ePlus.modules.community.api = {};
    }
}

ePlus.modules.community.api.exception = new CommunityShareException();
if (!window.ePlus.modules) {
    window.ePlus.modules = {};
}

if (!window.ePlus.modules.contacts) {
    window.ePlus.modules.contacts = {};
}

ePlus.modules.contacts.ui = function () {
    var updateStateProvinceSelect = function updateStateProvinceSelect(countrySelector) {
        var countryCodeParam = 'countryCode=' + countrySelector.selectedOptions[0].value;
        var url = "/GetTreelineControl.aspx?controlName=/uc/address/StateProvinceSelector.ascx&" + countryCodeParam;
        $('#state-province-input-container').load(url);
    };

    return {
        updateStateProvinceSelect: updateStateProvinceSelect
    };
}();
if (!window.ePlus.modules) {
    window.ePlus.modules = {};
}

if (!window.ePlus.modules.newsletterBanner) {
    window.ePlus.modules.newsletterBanner = {};
}

ePlus.modules.newsletterBanner.api = function () {
    var TreelinePaymentTopBannerSku = 'Monthly Newsletter Top Banner';
    var TreelinePaymentContentBannerSku = 'Monthly Newsletter Content Banner';
    var UkTopBannerSku = 'UK Monthly Newsletter Top Banner';
    var UkContentBannerSku = 'UK Monthly Newsletter Content Banner';

    var purchase = function purchase(newsletterBanner, bannerName) {
        var banners = [];
        var requestDate = new Date(new Date().setUTCHours(0, 0, 0, 0));

        var startDate = new Date(new Date(newsletterBanner.date).setUTCHours(0, 0, 0, 0));
        var endDate = getNewsletterBannerEndDate(newsletterBanner.date);

        banners.push({
            type: newsletterBanner.advertisementType,
            startDate: startDate.toISOString(),
            endDate: endDate.toISOString(),
            requestedByAppUserId: newsletterBanner.appUserId,
            requestDate: requestDate.toISOString(),
            fileUri: bannerName,
            actionUrl: $("#banner-action-url").val()
        });

        ePlus.modules.promotion.createFeaturedBanners(banners, function (data) {
            if (data != null) {
                var referenceIds = [];

                for (var i = 0; i < data.length; i++) {
                    referenceIds.push(data[i].id);
                }

                openPaymentModal({
                    referenceIds: referenceIds,
                    sku: getProductSku(newsletterBanner.advertisementType),
                    onClose: function onClose() {
                        closePaymentModal();
                        var sku = newsletterBanner.sku;
                        var promoteModalId = getPromoteModalId(sku);
                        var $promoteModalContent = $('#pop-modal-inner-' + promoteModalId);

                        $promoteModalContent.load('/promotions/' + sku + '/newsletters/' + newsletterBanner.newsletterType + '/banners/availability');
                    }
                });
            }
        });
    };

    function getNewsletterBannerEndDate(date) {
        var daysUntilNextNewsletter = 28;
        var endDate = new Date(new Date(date).setUTCHours(0, 0, 0, 0));
        endDate.setDate(endDate.getDate() + daysUntilNextNewsletter);

        return endDate;
    }

    function getProductSku(advertisementType) {
        switch (ePlus.util.getEnumValue('advertisementType', advertisementType.toUpperCase())) {
            case ePlus.util.getEnumValue('advertisementType', 'MONTHLYLIBRARIANNEWSLETTERTOPBANNER'):
            case ePlus.util.getEnumValue('advertisementType', 'MONTHLYBOOKSELLERNEWSLETTERTOPBANNER'):
                return TreelinePaymentTopBannerSku;
            case ePlus.util.getEnumValue('advertisementType', 'MONTHLYLIBRARIANNEWSLETTERCONTENTBANNER'):
            case ePlus.util.getEnumValue('advertisementType', 'MONTHLYBOOKSELLERNEWSLETTERCONTENTBANNER'):
                return TreelinePaymentContentBannerSku;
            case ePlus.util.getEnumValue('advertisementType', 'MONTHLYUKNEWSLETTERTOPBANNER'):
                return UkTopBannerSku;
            case ePlus.util.getEnumValue('advertisementType', 'MONTHLYUKNEWSLETTERCONTENTBANNER'):
                return UkContentBannerSku;
            default:
                return '';
        }
    }

    return {
        purchase: purchase
    };
}();
if (!window.ePlus.modules) {
    window.ePlus.modules = {};
}

if (!window.ePlus.modules.newsletterBanner) {
    window.ePlus.modules.newsletterBanner = {};
}

if (!window.ePlus.modules.newsletterBanner.monthly) {
    window.ePlus.modules.newsletterBanner.monthly = {};
}

ePlus.modules.newsletterBanner.monthly.ui = function () {
    var initializeAvailability = function initializeAvailability(sku, newsletterType, exampleImageUri) {
        $('#cancel-reservation-screen').on('click', function () {
            var $promoteModalContent = getPromoteModalJQueryElement(sku);

            $promoteModalContent.load('promotions/' + sku + '/newsletters/banners');
        });

        $('#promotion-banner-help').webuiPopover({
            content: '<img src=\'' + exampleImageUri + '\' alt=\'' + ePlus.resources.getRes('newsletter_banner_example_image') + '\' />',
            container: '#pageContent',
            backdrop: true,
            cache: false,
            animation: 'pop',
            placement: 'bottom'
        });

        $('.promote-reserve-button:enabled').on('click', function () {
            var $this = $(this);
            var newsletterDate = $this.data('date');
            var advertisementType = $this.data('advertisement-type');
            var url = '/promotions/' + sku + '/newsletters/' + newsletterType + '/advertisements/' + advertisementType + '/banners/reserve?date=' + encodeURIComponent(newsletterDate);

            var $promoteModalContent = getPromoteModalJQueryElement(sku);
            $promoteModalContent.load(url);
        });

        $('.modify-reservation-button').off('click').on('click', function () {
            var id = $(this).data('id');
            loadModifyReservationContent(sku, newsletterType, id);
        });
    };

    var intializeAdminAvailability = function intializeAdminAvailability(newsletterType) {
        var sku = '0';
        $('#cancel-reservation-screen').on('click', function () {
            var $promoteModalContent = getPromoteModalJQueryElement(sku);

            $promoteModalContent.load('promotions/' + sku + '/newsletters/banners');
        });

        $('.promote-reserve-button:enabled').on('click', function () {
            var $this = $(this);
            var newsletterDate = $this.data('date');
            var advertisementType = $this.data('advertisement-type');
            var url = '/promotions/admin/newsletters/' + newsletterType + '/advertisements/' + advertisementType + '/banners/reservations/view?date=' + encodeURIComponent(newsletterDate);

            var $promoteModalContent = getPromoteModalJQueryElement(sku);
            $promoteModalContent.load(url);
        });
    };

    var initializeBannerOptions = function initializeBannerOptions(sku, adCenterUrl, monthlyNewsletters) {
        var $promoteModalContent = getPromoteModalJQueryElement(sku);
        var monthlyLibrarian = monthlyNewsletters.monthlyLibrarian,
            monthlyBookseller = monthlyNewsletters.monthlyBookseller,
            monthlyUk = monthlyNewsletters.monthlyUk;


        $('#librarian-newsletter').on('click', function () {
            var url = adCenterUrl + 'newsletters?newsletterTypeId=' + monthlyLibrarian;
            window.open(url, '_blank').focus();
        });

        $('#bookseller-newsletter').on('click', function () {
            var url = adCenterUrl + 'newsletters?newsletterTypeId=' + monthlyBookseller;
            window.open(url, '_blank').focus();
        });

        $('#uk-newsletter').on('click', function () {
            var url = adCenterUrl + 'newsletters?newsletterTypeId=' + monthlyUk;
            window.open(url, '_blank').focus();
        });

        $('#cancel-targeted-newsletter').on('click', function () {
            $promoteModalContent.load(getPromoteModalUrl(sku));
        });
    };

    var initializeReservation = function initializeReservation(newsletterBanner) {
        var $bannerActionUrl = $('#banner-action-url');

        $('#test-url').off('click').on('click', function () {
            var url = $bannerActionUrl.val();
            window.open(url, '_blank');
        });

        $('#clear-url').off('click').on('click', function () {
            $bannerActionUrl.val('');
        });

        $('#cancel-banner-upload').on('click', function () {
            var sku = newsletterBanner.sku,
                newsletterType = newsletterBanner.newsletterType;

            var $promoteModalContent = getPromoteModalJQueryElement(sku);

            $promoteModalContent.load('/promotions/' + sku + '/newsletters/' + newsletterType + '/banners/availability');
        });

        $('#next-btn').off('click').on('click', function () {
            var confirmMessage = window.ePlus.resources.getRes('reserve_newsletter_banner_without_image');
            if (confirm(confirmMessage)) {
                submitBannerAndPurchase(newsletterBanner, '');
            }
        });

        $('#banner-files').fileupload({
            url: 'api/advertising/' + newsletterBanner.newsletterType + '/image/',
            xhrFields: {
                withCredentials: true
            },
            autoUpload: false,
            replaceFileInput: false,
            dataType: 'json',
            add: function add(e, data) {
                var $nextButton = $('#next-btn');

                $nextButton.removeClass('disabled');
                $nextButton.off('click').on('click', function () {
                    if ($bannerActionUrl.val() === '') {
                        alert(ePlus.resources.getRes('banner_redirect_url_required'));
                        return;
                    }

                    setMessage(ePlus.resources.getRes('uploading_file') + '...');
                    data.submit();
                });
            },
            done: function done(e, data) {
                var filename = data.result[0];
                submitBannerAndPurchase(newsletterBanner, filename);
            },
            fail: function fail(e, data) {
                clearMessage();
                alert(JSON.parse(data.jqXHR.responseText).message);
            }
        });
    };

    var submitBannerAndPurchase = function submitBannerAndPurchase(newsletterBanner, filename) {
        clearMessage();
        bannerUploadComplete();
        ePlus.modules.newsletterBanner.api.purchase(newsletterBanner, filename);
    };

    var initializeReservationAdmin = function initializeReservationAdmin(newsletterType) {
        var sku = 0;
        $('#back-to-admin-availability').on('click', function () {
            var $promoteModalContent = getPromoteModalJQueryElement(sku);

            $promoteModalContent.load('/promotions/' + sku + '/newsletters/' + newsletterType + '/banners/availability');
        });
    };

    var initializeModifyReservation = function initializeModifyReservation(newsletterBanner) {
        var $bannerActionUrl = $('#banner-action-url');
        $('#test-url').off('click').on('click', function () {
            var url = $bannerActionUrl.val();
            window.open(url, '_blank');
        });

        $('#clear-url').off('click').on('click', function () {
            $bannerActionUrl.val('');
        });

        $('#cancel-banner-upload').on('click', function () {
            var sku = newsletterBanner.sku,
                newsletterType = newsletterBanner.newsletterType;


            var url = '/promotions/' + sku + '/newsletters/' + newsletterType + '/banners/availability';

            var $promoteModalContent = getPromoteModalJQueryElement(newsletterBanner.sku);
            $promoteModalContent.load(url);
        });

        $('#banner-files').fileupload({
            url: 'api/advertising/' + newsletterBanner.newsletterType + '/image/',
            xhrFields: {
                withCredentials: true
            },
            autoUpload: false,
            replaceFileInput: false,
            dataType: 'json',
            add: function add(e, data) {
                var $nextButton = $('#next-btn');

                $nextButton.removeClass('disabled');
                $nextButton.off('click').on('click', function () {
                    if ($bannerActionUrl.val() === '') {
                        alert(ePlus.resources.getRes('banner_redirect_url_required'));
                        return;
                    }

                    setMessage(ePlus.resources.getRes('uploading_file') + '...');
                    data.submit();
                });
            },
            done: function done(e, data) {
                newsletterBanner.fileUri = data.result[0];
                newsletterBanner.actionUrl = $bannerActionUrl.val();

                var sku = newsletterBanner.sku,
                    newsletterType = newsletterBanner.newsletterType,
                    id = newsletterBanner.id;

                ePlus.modules.promotion.updateFeaturedBanner(newsletterBanner, function () {
                    loadModifyReservationContent(sku, newsletterType, id);
                });
            },
            fail: function fail(e, data) {
                clearMessage();
                alert(JSON.parse(data.jqXHR.responseText).message);
            }
        });
    };

    function bannerUploadComplete() {
        setMessage(ePlus.resources.getRes('upload_complete'));
    }

    function setMessage(message, isError) {
        $('#banner-status-message').html(message);
        if (isError) {
            $('#banner-status-message').addClass('error-color');
        } else {
            $('#banner-status-message').removeClass('error-color');
        }
    }

    function clearMessage() {
        setMessage('&nbsp;');
    }

    var getPromoteModalJQueryElement = function getPromoteModalJQueryElement(sku) {
        var promoteModalId = getPromoteModalId(sku);
        return $('#pop-modal-inner-' + promoteModalId);
    };

    var initializeReservationError = function initializeReservationError(sku, newsletterType) {
        $('#cancel-banner-upload').on('click', function () {
            var $promoteModalContent = getPromoteModalJQueryElement(sku);
            $promoteModalContent.load('/promotions/' + sku + '/newsletters/' + newsletterType + '/banners/availability');
        });
    };

    var loadModifyReservationContent = function loadModifyReservationContent(sku, newsletterType, id) {
        var url = 'promotions/' + sku + '/newsletters/' + newsletterType + '/advertisements/banners/reservations/' + id + '/modify';

        var $promoteModalContent = getPromoteModalJQueryElement(sku);
        $promoteModalContent.load(url);
    };

    return {
        initializeAvailability: initializeAvailability,
        intializeAdminAvailability: intializeAdminAvailability,
        initializeBannerOptions: initializeBannerOptions,
        initializeReservation: initializeReservation,
        initializeReservationAdmin: initializeReservationAdmin,
        initializeReservationError: initializeReservationError,
        initializeModifyReservation: initializeModifyReservation,
        clearMessage: clearMessage
    };
}();
if (!window.ePlus.modules) {
    window.ePlus.modules = {};
}

if (!window.ePlus.modules.newsletterBanner) {
    window.ePlus.modules.newsletterBanner = {};
}

ePlus.modules.newsletterBanner.ui = function () {
    var selectedPrimaryBannerDate = null;
    var selectedSecondaryBannerDate = null;

    var bannerName = '';
    var config = {};

    var getFileUri = function getFileUri() {
        return $('#file-uri').val().trim();
    };

    var setFileUri = function setFileUri(uri) {
        $('#file-uri').val(uri);
    };

    var getBannerActionUrl = function getBannerActionUrl() {
        return $('#banner-action-url').val().trim();
    };

    var hasValidBannerActionUrl = function hasValidBannerActionUrl() {
        return getBannerActionUrl() !== '';
    };

    var fileUploadOnAddFile = function fileUploadOnAddFile(e, data) {
        $('#next-btn').off('click').on('click', function () {
            if (!hasValidDateSelection()) {
                alert(getRes('please_select_dates_reserve'));
            } else if (!hasValidBannerActionUrl()) {
                alert(getRes('banner_redirect_url_required'));
            } else {
                setMessage(getRes('uploading_file') + '...');
                data.submit();
            }
        });
    };

    var setMessage = function setMessage(message, isError) {
        $('#banner-status-message').html(message);

        if (isError) {
            $('#banner-status-message').addClass("error-color");
        } else {
            $('#banner-status-message').removeClass("error-color");
        }
    };

    var bannerUploadComplete = function bannerUploadComplete(fileName) {
        bannerName = fileName;
        setMessage(getRes('upload_complete'));
    };

    var submitBannerAndPurchase = function submitBannerAndPurchase(filename) {
        setMessage('&nbsp;');
        bannerUploadComplete(filename);
        purchase();
    };

    var submitWithImageUpload = function submitWithImageUpload(e, data) {
        var fileName = data.result[0];

        submitBannerAndPurchase(fileName);
    };

    var hasValidDateSelection = function hasValidDateSelection() {
        return selectedPrimaryBannerDate || selectedSecondaryBannerDate;
    };

    var submitWithoutImageUpload = function submitWithoutImageUpload() {
        if (!hasValidDateSelection()) {
            alert(getRes('please_select_dates_reserve'));
            return;
        }

        var confirmMessage = getRes('submit_banner_no_image_no_url');

        if (hasValidBannerActionUrl()) {
            confirmMessage = getRes('submit_banner_no_image');
        }

        if (confirm(confirmMessage)) {
            submitBannerAndPurchase('');
        }
    };

    var createBanner = function createBanner(date, type) {
        var requestDate = new Date(new Date().setUTCHours(0, 0, 0, 0));
        var startDate = new Date(new Date(date).setUTCHours(0, 0, 0, 0));
        var endDate = new Date(new Date(date).setUTCHours(0, 0, 0, 0));

        endDate.setDate(endDate.getDate() + 7);

        return {
            type: type,
            startDate: startDate.toISOString(),
            endDate: endDate.toISOString(),
            requestedByAppUserId: config.appUserId,
            requestDate: requestDate.toISOString(),
            fileUri: bannerName,
            actionUrl: getBannerActionUrl()
        };
    };

    var getPrimaryBannerPaymentSku = function getPrimaryBannerPaymentSku() {
        if (config.primaryBannerType === ePlus.util.getEnumValue('advertisementType', 'UKPRIMARYNEWSLETTERBANNER')) {
            return ePlus.modules.paymentControl.products.UK_NEWSLETTER_BANNER;
        }

        return ePlus.modules.paymentControl.products.NEWSLETTER_BANNER;
    };

    var getSecondaryBannerPaymentSku = function getSecondaryBannerPaymentSku() {
        if (config.secondaryBannerType === ePlus.util.getEnumValue('advertisementType', 'UKSECONDARYNEWSLETTERBANNER')) {
            return ePlus.modules.paymentControl.products.UK_NEWSLETTER_BANNER_CONTENT;
        }

        return ePlus.modules.paymentControl.products.NEWSLETTER_BANNER_CONTENT;
    };

    var purchase = function purchase() {
        var banners = [];
        var sku = '';

        if (selectedPrimaryBannerDate) {
            banners.push(createBanner(selectedPrimaryBannerDate, config.primaryBannerType));
            sku = getPrimaryBannerPaymentSku();
        } else if (selectedSecondaryBannerDate) {
            banners.push(createBanner(selectedSecondaryBannerDate, config.secondaryBannerType));
            sku = getSecondaryBannerPaymentSku();
        }

        ePlus.modules.promotion.createFeaturedBanners(banners, function (data) {
            if (data != null) {
                closeReserveFeaturedBannerModal();

                var referenceIds = [];

                for (var i = 0; i < data.length; i++) {
                    referenceIds.push(data[i].id);
                }

                openPaymentModal({
                    referenceIds: referenceIds,
                    sku: sku
                });
            }
        });
    };

    var initializeWeeklyNewsletterBannerAvailability = function initializeWeeklyNewsletterBannerAvailability(conf) {
        config = conf;

        selectedPrimaryBannerDate = null;
        selectedSecondaryBannerDate = null;

        bannerName = '';

        $(document).bind('drop dragover', function (e) {
            e.preventDefault();
        });

        $('.reserved-banner').off('click').on('click', function () {
            var $this = $(this);
            var url = '/GetTreelineControl.aspx?controlName=/uc/promote/modifyFeaturedBanner.ascx&adId=' + $this.data('ad-id');
            $('#pop-modal-inner-' + getReserveFeaturedBannerModalId()).load(url);
        });

        $('#test-url-btn').off('click').on('click', function () {
            var url = getBannerActionUrl();
            window.open(url, '_blank');
        });

        $('#clear-url-btn').off('click').on('click', function () {
            $('#banner-action-url').val('');
        });

        $('#cancel-btn').off('click').on('click', function () {
            closeReserveFeaturedBannerModal();
        });

        $('#next-btn').off('click').on('click', function () {
            submitWithoutImageUpload();
        });

        $('.newsletter-primary-checkbox').on('change', function () {
            var date = $(this).attr('data-date');
            var checked = $(this).prop('checked');

            if (checked) {
                selectedPrimaryBannerDate = date;
                selectedSecondaryBannerDate = null;
            } else {
                selectedPrimaryBannerDate = null;
            }

            $('.newsletter-primary-checkbox').not($(this)).prop('checked', false);
            $('.newsletter-secondary-checkbox').prop('checked', false);
        });

        $('.newsletter-secondary-checkbox').on('change', function () {
            var date = $(this).attr('data-date');
            var checked = $(this).prop('checked');

            if (checked) {
                selectedSecondaryBannerDate = date;
                selectedPrimaryBannerDate = null;
            } else {
                selectedSecondaryBannerDate = null;
            }

            $('.newsletter-primary-checkbox').prop('checked', false);
            $('.newsletter-secondary-checkbox').not($(this)).prop('checked', false);
        });

        $('#file').fileupload({
            url: "api/advertising/featuredBanners/image/",
            xhrFields: {
                withCredentials: true
            },
            autoUpload: false,
            replaceFileInput: false,
            dataType: 'json',
            add: function add(e, data) {
                fileUploadOnAddFile(e, data);
            },
            done: function done(e, data) {
                submitWithImageUpload(e, data);
            },
            fail: function fail(e, data) {
                setMessage('&nbsp;');
                alert(JSON.parse(data.jqXHR.responseText).message);
            }
        });
    };

    var initializeModifyFeaturedBanner = function initializeModifyFeaturedBanner(newsletterBanner) {
        $('#back-btn').off('click').on('click', function () {
            var url = '/promotions/weeklyNewsletterBannerAvailability';
            $('#pop-modal-inner-' + getReserveFeaturedBannerModalId()).load(url);
        });

        $('#test-url-btn').off('click').on('click', function () {
            var url = getBannerActionUrl();
            window.open(url, '_blank');
        });

        $('#clear-url-btn').off('click').on('click', function () {
            $('#banner-action-url').val('');
        });

        $('#save-btn').off().on('click', function () {
            newsletterBanner.fileUri = getFileUri();
            newsletterBanner.actionUrl = getBannerActionUrl();

            ePlus.modules.promotion.updateFeaturedBanner(newsletterBanner, function () {
                var url = '/GetTreelineControl.aspx?controlName=/uc/promote/modifyFeaturedBanner.ascx&adId=' + newsletterBanner.id;
                $('#pop-modal-inner-' + getReserveFeaturedBannerModalId()).load(url);
                alert(getRes('upload_complete'));
            });
        });

        $('#file').fileupload({
            url: 'api/advertising/featuredBanners/image/',
            xhrFields: {
                withCredentials: true
            },
            autoUpload: false,
            replaceFileInput: false,
            dataType: 'json',
            add: function add(e, data) {
                data.submit();
            },
            done: function done(e, data) {
                var fileName = data.result[0];
                newsletterBanner.fileUri = fileName;
                newsletterBanner.actionUrl = getBannerActionUrl();

                setFileUri(fileName);

                ePlus.modules.promotion.updateFeaturedBanner(newsletterBanner, function () {
                    var url = '/GetTreelineControl.aspx?controlName=/uc/promote/modifyFeaturedBanner.ascx&adId=' + newsletterBanner.id;
                    $('#pop-modal-inner-' + getReserveFeaturedBannerModalId()).load(url);
                    alert(getRes('upload_complete'));
                });
            },
            fail: function fail(e, data) {
                alert(JSON.parse(data.jqXHR.responseText).message);
            }
        });
    };

    return {
        initializeWeeklyNewsletterBannerAvailability: initializeWeeklyNewsletterBannerAvailability,
        initializeModifyFeaturedBanner: initializeModifyFeaturedBanner
    };
}();
if (!window.ePlus.modules.preferences) {
    window.ePlus.modules.preferences = {};
}

window.ePlus.modules.preferences.landingpage = {};

window.ePlus.modules.preferences.landingpage.api = function () {
    var setLandingPageToCommunity = function setLandingPageToCommunity(callback) {
        var value = 'Community';

        postPreference(value, function () {
            setCookie(value);
            if (typeof callback === 'function') {
                callback();
            }
        });
    };

    var setLandingPageToEdelweissPlus = function setLandingPageToEdelweissPlus(callback) {
        var value = 'EdelweissPlus';

        postPreference(value, function () {
            setCookie(value);
            if (typeof callback === 'function') {
                callback();
            }
        });
    };

    var postPreference = function postPreference(value, callback) {
        var url = '/api/me/preferences/LandingPage/preferred';

        $.ajax({
            type: 'POST',
            url: url,
            contentType: 'application/json',
            data: JSON.stringify(value)
        }).done(function () {
            callback();
        });
    };

    var setCookie = function setCookie(value) {
        var hostname = window.location.hostname;
        var hostnameComponents = hostname.split('.');

        var domain = hostname;
        if (hostnameComponents.length > 2) {
            var length = hostnameComponents.length;
            domain = '.' + hostnameComponents[length - 2] + '.' + hostnameComponents[length - 1];
        }

        var date = new Date();
        date.setHours(date.getHours() + 1);
        $.cookie("treeline.landingpage", value, {
            domain: domain,
            expires: date,
            path: '/;SameSite=Strict'
        });
    };

    return {
        setLandingPageToCommunity: setLandingPageToCommunity,
        setLandingPageToEdelweissPlus: setLandingPageToEdelweissPlus
    };
}();
(function () {
    var _affiliationModalId = Symbol('affiliationModalId');
    var _getIncludedCommunityTypes = Symbol('getIncludedCommunityTypes');
    var _getModalJQuerySelector = Symbol('getModalJQuerySelector');
    var _initialCommunityTypeState = Symbol('initialCommunityTypeState');
    var _initialAffiliationTypeState = Symbol('initialAffiliationTypeState');
    var _initializeCommunityTypeState = Symbol('initializeCommunityTypeStates');
    var _modalId = Symbol('modalId');
    var _updateAffiliationCatalogInclusionsCheckbox = Symbol('updateAffiliationCatalogInclusionsCheckbox');
    var _updateAffiliationCatalogInclusions = Symbol('updateAffiliationCatalogInclusions');
    var _affiliationShareCatalogInclusionDialog = Symbol('affiliationShareCatalogInclusionDialog');
    var _updateCatalogShareIconHighlightedStatus = Symbol('updateCatalogShareIconHighlightedStatus');

    var affiliationCommunityType = ePlus.util.getEnumValue('communityType', 'AFFILIATIONS');
    var affiliationTypeStateOverrides = [];

    var AffiliationShareCatalogInclusionDialog = function () {
        function AffiliationShareCatalogInclusionDialog() {
            _classCallCheck(this, AffiliationShareCatalogInclusionDialog);

            this[_initialAffiliationTypeState] = [];
            this[_affiliationModalId] = 'AffiliationShareExceptionsModal';
            this.affiliationTypeState = [];
        }

        _createClass(AffiliationShareCatalogInclusionDialog, [{
            key: 'doSaveAffiliationShareTypes',
            value: function doSaveAffiliationShareTypes() {
                var $communityTypeCheckbox = $('#community-type-' + affiliationCommunityType);
                return $communityTypeCheckbox.is('.box_partial_checked') || $communityTypeCheckbox.is('.box_checked');
            }
        }, {
            key: 'getAffiliationModalJQuerySelector',
            value: function getAffiliationModalJQuerySelector() {
                return '#pop-modal-inner-' + this[_affiliationModalId];
            }
        }, {
            key: 'initializeAffiliationTypeState',
            value: function initializeAffiliationTypeState() {
                this[_initialAffiliationTypeState] = [];
                var ats = this[_initialAffiliationTypeState];
                $('.community-share-exception-checkbox', $(this.getAffiliationModalJQuerySelector)).each(function () {
                    var $checkBox = $(this);
                    var affiliationShareType = {
                        targetGroupId: $checkBox.data('community-type'),
                        isUnchecked: $checkBox.hasClass('box_unchecked')
                    };

                    ats.push(affiliationShareType);
                });
            }
        }, {
            key: 'getIncludedAffiliations',
            value: function getIncludedAffiliations() {
                var affiliationShareTypes = [];
                $('.community-share-exception-checkbox', $(this.getAffiliationModalJQuerySelector())).each(function () {
                    var $checkBox = $(this);
                    var affiliationShareType = {
                        targetGroupId: $checkBox.data('affiliation-id'),
                        isChecked: $checkBox.hasClass('box_checked')
                    };

                    affiliationShareTypes.push(affiliationShareType);
                });

                return affiliationShareTypes;
            }
        }, {
            key: 'getIncludedAffiliationTypes',
            value: function getIncludedAffiliationTypes() {
                return this.affiliationTypeState.filter(function (element) {
                    return element.isChecked;
                });
            }
        }, {
            key: 'openAffiliationCatalogInclusionModal',
            value: function openAffiliationCatalogInclusionModal(catalogId, appUserId) {
                var _this = this;

                var url = 'catalogs/' + catalogId + '/users/' + appUserId + '/communities/shares-inclusions/affiliations';

                var $affiliationCommunityType = $('#community-type-' + affiliationCommunityType);
                if (!$affiliationCommunityType.hasClass('box_partial_checked')) {
                    var areAllAffiliationsExcluded = $('#community-type-' + affiliationCommunityType).hasClass('box_unchecked');
                    url += '?areAllAffiliationsExcluded=' + areAllAffiliationsExcluded;
                }

                var affiliationModalJQuerySelector = this.getAffiliationModalJQuerySelector();

                openMultiModal({
                    id: this[_affiliationModalId],
                    url: url,
                    width: '300px',
                    height: '400px',
                    onLoad: function onLoad() {
                        $("#pop-modal-content-AffiliationShareExceptionsModal").zIndex($("#pop-modal-content-CommunityShareExceptionsModal").zIndex() + 1);

                        $('.community-share-exception-checkbox', $(affiliationModalJQuerySelector)).on('click', function (event) {
                            window.enableSingleCheckbox($(event.currentTarget));
                        });

                        $('#affiliation-share-exception-save', $(affiliationModalJQuerySelector)).on('click', function () {
                            _this.affiliationTypeState = _this.getIncludedAffiliations();
                            affiliationTypeStateOverrides = _this.affiliationTypeState;
                            _this[_updateAffiliationCatalogInclusionsCheckbox]();
                            closeMultiModal(_this[_affiliationModalId]);
                        });

                        _this.updateAffiliationCheckboxStateWithGlobalOverride();
                        _this.initializeAffiliationTypeState();
                    },
                    onClose: function onClose() {
                        closeMultiModal(_this[_affiliationModalId]);
                    },
                    isFixup: true
                });
            }
        }, {
            key: _updateAffiliationCatalogInclusionsCheckbox,
            value: function value() {
                var includedAffiliations = this.getIncludedAffiliationTypes();
                var $affiliationCheckBox = $('#community-type-' + affiliationCommunityType);

                if (this.affiliationTypeState.length === includedAffiliations.length) {
                    $affiliationCheckBox.addClass('box_checked').removeClass('box_unchecked box_partial_checked');
                } else if (includedAffiliations.length > 0) {
                    $affiliationCheckBox.addClass('box_partial_checked').removeClass('box_checked box_unchecked');
                } else {
                    $affiliationCheckBox.addClass('box_unchecked').removeClass('box_checked box_partial_checked');
                }
            }
        }, {
            key: 'updateAffiliationCheckboxStateWithGlobalOverride',
            value: function updateAffiliationCheckboxStateWithGlobalOverride() {
                $(".affiliation-share-exception-list .community-share-exception-checkbox").each(function (index, checkbox) {
                    var $checkbox = $(checkbox);
                    var affiliationId = parseInt($checkbox.attr("data-affiliation-id"));
                    var ats = affiliationTypeStateOverrides.find(function (ats) {
                        return ats.targetGroupId === affiliationId;
                    });

                    if (typeof ats !== "undefined") {
                        var isChecked = $checkbox.hasClass("box_checked");
                        if (isChecked && !ats.isChecked) {
                            ePlus.ui.makeBoxUnchecked($checkbox);
                        } else if (!isChecked && ats.isChecked) {
                            ePlus.ui.makeBoxChecked($checkbox);
                        }
                    }
                });
            }
        }]);

        return AffiliationShareCatalogInclusionDialog;
    }();

    var CommunityShareCatalogInclusionDialog = function () {
        function CommunityShareCatalogInclusionDialog() {
            _classCallCheck(this, CommunityShareCatalogInclusionDialog);

            this[_initialCommunityTypeState] = [];
            this[_modalId] = 'CommunityShareExceptionsModal';
            this[_affiliationShareCatalogInclusionDialog] = new AffiliationShareCatalogInclusionDialog();
        }

        _createClass(CommunityShareCatalogInclusionDialog, [{
            key: 'hasInitialCommunityTypeStatesChanged',
            value: function hasInitialCommunityTypeStatesChanged() {
                var hasChanged = false;
                var cts = this[_initialCommunityTypeState];
                $('.community-share-exception-checkbox', $(this[_getModalJQuerySelector]())).each(function (_, elem) {
                    var checkBox = $(elem);
                    hasChanged = cts.some(function (cts) {
                        return cts.communityType === checkBox.data('community-type') && cts.isUnchecked !== checkBox.hasClass('box_unchecked');
                    });

                    if (hasChanged) {
                        return false;
                    }
                });

                return hasChanged;
            }
        }, {
            key: _getIncludedCommunityTypes,
            value: function value() {
                var communityShareTypes = [];
                var affiliationShareTypes = [];

                $('.community-share-exception-checkbox[data-community-type]', $(this[_getModalJQuerySelector]())).each(function () {
                    var $checkBox = $(this);
                    var communityType = $checkBox.data('community-type');
                    if (communityType === affiliationCommunityType && $checkBox.is('.unbox_checked, .box_partial_checked')) {
                        var affiliationShareType = {
                            communityType: communityType
                        };
                        affiliationShareTypes.push(affiliationShareType);
                    } else if ($checkBox.hasClass('box_checked')) {
                        var communityShareType = {
                            communityType: communityType
                        };
                        communityShareTypes.push(communityShareType);
                    }
                });

                return {
                    communityShareTypes: communityShareTypes,
                    affiliationShareTypes: affiliationShareTypes
                };
            }
        }, {
            key: _getModalJQuerySelector,
            value: function value() {
                return '#pop-modal-inner-' + this[_modalId];
            }
        }, {
            key: _initializeCommunityTypeState,
            value: function value() {
                this[_initialCommunityTypeState] = [];
                var cts = this[_initialCommunityTypeState];
                $('.community-share-exception-checkbox', $(this[_getModalJQuerySelector]())).each(function () {
                    var $checkBox = $(this);
                    var communityShareType = {
                        communityType: $checkBox.data('community-type'),
                        isUnchecked: $checkBox.hasClass('box_unchecked')
                    };

                    cts.push(communityShareType);
                });
            }
        }, {
            key: 'initializeCommunityShareCatalogInclusion',
            value: function initializeCommunityShareCatalogInclusion(catalogId, appUserId) {
                var _this2 = this;

                var url = '/catalogs/' + catalogId + '/users/' + appUserId + '/communities/shares-inclusions';
                openMultiModal({
                    id: this[_modalId],
                    url: url,
                    width: '300px',
                    height: '250px',
                    onLoad: function onLoad() {
                        affiliationTypeStateOverrides = [];

                        $('.community-share-exception-checkbox', $(_this2[_getModalJQuerySelector]())).on('click', function () {
                            var $this = $(this);
                            window.enableSingleCheckbox($this);

                            if ($this.data('community-type') === affiliationCommunityType) {
                                affiliationTypeStateOverrides = [];
                            }
                        });

                        $('#community-share-exception-save').on('click', function (event) {
                            var includedCommunityTypes = _this2[_getIncludedCommunityTypes]();

                            ePlus.modules.community.api.catalogInclusion.updateCommunityShareCatalogInclusions('PUT', $(event.currentTarget).attr('data-share-type-id'), $(event.currentTarget).data('app-user-id'), includedCommunityTypes.communityShareTypes).done(function () {
                                _this2[_updateAffiliationCatalogInclusions](catalogId, appUserId);

                                if (ePlus.modules.community.api.catalogInclusion.doUpdateColleagueShare()) {
                                    ePlus.modules.community.api.catalogInclusion.updateColleagueShare(catalogId);
                                }

                                _this2[_updateCatalogShareIconHighlightedStatus](catalogId, includedCommunityTypes);

                                closeMultiModal(_this2[_modalId]);
                            }).fail(function () {
                                alert(getRes('error_unexpected'));
                            });
                        });

                        $('#affiliation-share-exception').on('click', function () {
                            _this2[_affiliationShareCatalogInclusionDialog].openAffiliationCatalogInclusionModal(catalogId, appUserId);
                        });

                        $("#selectAllCommunityShareExceptions").on("click", function () {
                            $(".community-share-exception-checkbox").removeClass("box_unchecked").addClass("box_checked");
                        });

                        $("#unselectAllCommunityShareExceptions").on("click", function () {
                            $(".community-share-exception-checkbox").removeClass("box_checked").addClass("box_unchecked");
                        });

                        _this2[_initializeCommunityTypeState]();
                    },
                    onClose: function onClose() {
                        if (_this2.hasInitialCommunityTypeStatesChanged()) {
                            return confirm(getRes('you_have_unsaved_changes_will_be_lost'));
                        }

                        return true;
                    }
                });
            }
        }, {
            key: _updateAffiliationCatalogInclusions,
            value: function value(catalogId, appUserId) {
                if (this[_affiliationShareCatalogInclusionDialog].doSaveAffiliationShareTypes()) {
                    return ePlus.modules.community.api.catalogInclusion.saveAffiliationShareCatalogInclusions(catalogId, appUserId, this[_affiliationShareCatalogInclusionDialog].getIncludedAffiliationTypes());
                }
            }
        }, {
            key: _updateCatalogShareIconHighlightedStatus,
            value: function value(catalogId, communityShares) {
                var doHighlightSharedCatalogIcon = communityShares && (communityShares.affiliationShareTypes.length > 0 || communityShares.communityShareTypes.length > 0);
                $('#shareCol_' + catalogId).toggleClass('sharedCollection', doHighlightSharedCatalogIcon);
            }
        }]);

        return CommunityShareCatalogInclusionDialog;
    }();

    if (typeof ePlus.modules.community === 'undefined') {
        ePlus.modules.community = {};
    }

    if (typeof ePlus.modules.community.ui === 'undefined') {
        ePlus.modules.community.ui = {};
    }

    if (typeof ePlus.modules.community.ui.dialog === 'undefined') {
        ePlus.modules.community.ui.dialog = {};
    }

    ePlus.modules.community.ui.dialog.catalogInclusion = new CommunityShareCatalogInclusionDialog();
})();

(function () {
    var _affiliationModalId = Symbol('affiliationModalId');
    var _getExcludedCommunityTypes = Symbol('getExcludedCommunityTypes');
    var _updateCommunityShareExceptionPopover = Symbol('getCommunityShareExceptionPopover');
    var _getModalJQuerySelector = Symbol('getModalJQuerySelector');
    var _initialCommunityTypeState = Symbol('initialCommunityTypeState');
    var _initialAffiliationTypeState = Symbol('initialAffiliationTypeState');
    var _initializeCommunityTypeState = Symbol('initializeCommunityTypeStates');
    var _modalId = Symbol('modalId');
    var _updateAffiliationExceptionsCheckbox = Symbol('updateAffiliationExceptionsCheckbox');
    var _updateAffiliationExceptions = Symbol('updateAffiliationExceptions');
    var _affiliationShareExceptionDialog = Symbol('affiliationShareExceptionDialog');
    var _doSaveAsDefault = Symbol('saveAsDefault');

    var affiliationCommunityType = ePlus.util.getEnumValue('communityType', 'AFFILIATIONS');
    var affiliationTypeStateOverrides = [];

    var AffiliationShareExceptionDialog = function () {
        function AffiliationShareExceptionDialog() {
            _classCallCheck(this, AffiliationShareExceptionDialog);

            this[_initialAffiliationTypeState] = [];
            this[_affiliationModalId] = 'AffiliationShareExceptionsModal';
            this.affiliationTypeState = [];
        }

        _createClass(AffiliationShareExceptionDialog, [{
            key: 'doSaveAffiliationShareTypes',
            value: function doSaveAffiliationShareTypes() {
                var $communityTypeCheckbox = $('#community-type-' + affiliationCommunityType);
                return $communityTypeCheckbox.is('.box_partial_checked') || $communityTypeCheckbox.is('.box_checked');
            }
        }, {
            key: 'getAffiliationModalJQuerySelector',
            value: function getAffiliationModalJQuerySelector() {
                return '#pop-modal-inner-' + this[_affiliationModalId];
            }
        }, {
            key: 'initializeAffiliationTypeState',
            value: function initializeAffiliationTypeState() {
                this[_initialAffiliationTypeState] = [];
                var ats = this[_initialAffiliationTypeState];
                $('.community-share-exception-checkbox', $(this.getAffiliationModalJQuerySelector)).each(function () {
                    var $checkBox = $(this);
                    var affiliationShareType = {
                        targetGroupId: $checkBox.data('community-type'),
                        isUnchecked: $checkBox.hasClass('box_unchecked')
                    };

                    ats.push(affiliationShareType);
                });
            }
        }, {
            key: 'updateAffiliationCheckboxStateWithGlobalOverride',
            value: function updateAffiliationCheckboxStateWithGlobalOverride() {
                $(".affiliation-share-exception-list .community-share-exception-checkbox").each(function (index, checkbox) {
                    var $checkbox = $(checkbox);
                    var affiliationId = parseInt($checkbox.attr("data-affiliation-id"));
                    var ats = affiliationTypeStateOverrides.find(function (ats) {
                        return ats.targetGroupId == affiliationId;
                    });
                    if (typeof ats !== "undefined") {
                        var isChecked = $checkbox.hasClass("box_checked");
                        if (isChecked && ats.isUnchecked) {
                            ePlus.ui.makeBoxUnchecked($checkbox);
                        } else if (!isChecked && !ats.isUnchecked) {
                            ePlus.ui.makeBoxChecked($checkbox);
                        }
                    }
                });
            }
        }, {
            key: 'getAffiliationModalJQuerySelector',
            value: function getAffiliationModalJQuerySelector() {
                return '#pop-modal-inner-' + this[_affiliationModalId];
            }
        }, {
            key: 'getExcludedAffiliations',
            value: function getExcludedAffiliations() {
                var affiliationShareTypes = [];
                $('.community-share-exception-checkbox', $(this.getAffiliationModalJQuerySelector())).each(function () {
                    var $checkBox = $(this);
                    var affiliationShareType = {
                        targetGroupId: $checkBox.data('affiliation-id'),
                        isUnchecked: $checkBox.hasClass('box_unchecked')
                    };

                    affiliationShareTypes.push(affiliationShareType);
                });

                return affiliationShareTypes;
            }
        }, {
            key: 'getExcludedAffiliationTypes',
            value: function getExcludedAffiliationTypes() {
                return this.affiliationTypeState.filter(function (element) {
                    return element.isUnchecked;
                });
            }
        }, {
            key: 'openAffiliationExceptionModal',
            value: function openAffiliationExceptionModal(sku, appUserId) {
                var _this3 = this;

                var $affiliationCommunityType = $('#community-type-' + affiliationCommunityType);
                var url = 'products/' + sku + '/users/' + appUserId + '/communities/shares-exceptions/affiliations';

                if (!$affiliationCommunityType.hasClass('box_partial_checked')) {
                    var areAllAffiliationsExcluded = $('#community-type-' + affiliationCommunityType).hasClass('box_unchecked');
                    url += '?areAllAffiliationsExcluded=' + areAllAffiliationsExcluded;
                }

                var affiliationModalJQuerySelector = this.getAffiliationModalJQuerySelector();

                openMultiModal({
                    id: this[_affiliationModalId],
                    url: url,
                    width: '300px',
                    height: '400px',
                    isFixup: true,
                    onLoad: function onLoad() {
                        var self = _this3;

                        if ($("#pop-modal-content-CommunityShareExceptionsModal").length > 0) {
                            $("#pop-modal-content-AffiliationShareExceptionsModal").zIndex($("#pop-modal-content-CommunityShareExceptionsModal").zIndex() + 1);
                        } else {
                            $("#pop-modal-content-AffiliationShareExceptionsModal").zIndex(9000002);
                        }

                        $('.community-share-exception-checkbox', $(affiliationModalJQuerySelector)).on('click', function (event) {
                            window.enableSingleCheckbox($(event.currentTarget));
                        });

                        $('#affiliation-share-exception-save', $(affiliationModalJQuerySelector)).on('click', function () {
                            self.affiliationTypeState = self.getExcludedAffiliations();
                            affiliationTypeStateOverrides = self.affiliationTypeState;
                            self[_updateAffiliationExceptionsCheckbox]();
                            closeMultiModal(self[_affiliationModalId]);
                        });

                        if (!$affiliationCommunityType.hasClass('box_partial_checked')) {
                            var _areAllAffiliationsExcluded = $('#community-type-' + affiliationCommunityType).hasClass('box_unchecked');
                            affiliationTypeStateOverrides.map(function (ats) {
                                ats.isExcluded = _areAllAffiliationsExcluded;
                                return ats;
                            });
                        }

                        _this3.updateAffiliationCheckboxStateWithGlobalOverride();
                        _this3.initializeAffiliationTypeState();
                    },
                    onClose: function onClose() {
                        closeMultiModal(_this3[_affiliationModalId]);
                    }
                });
            }
        }, {
            key: _updateAffiliationExceptionsCheckbox,
            value: function value() {
                var excludedAffiliations = this.getExcludedAffiliationTypes();

                var $affiliationCheckBox = $('#community-type-' + affiliationCommunityType);
                if (this.affiliationTypeState.length === excludedAffiliations.length) {
                    $affiliationCheckBox.addClass('box_unchecked').removeClass('box_checked box_partial_checked');
                } else if (excludedAffiliations.length > 0) {
                    $affiliationCheckBox.addClass('box_partial_checked').removeClass('box_checked box_unchecked');
                } else {
                    $affiliationCheckBox.addClass('box_checked').removeClass('box_unchecked box_partial_checked');
                }
            }
        }]);

        return AffiliationShareExceptionDialog;
    }();

    var CommunityShareExceptionDialog = function () {
        function CommunityShareExceptionDialog() {
            _classCallCheck(this, CommunityShareExceptionDialog);

            this[_initialCommunityTypeState] = [];
            this[_modalId] = 'CommunityShareExceptionsModal';
            this[_affiliationShareExceptionDialog] = new AffiliationShareExceptionDialog();
        }

        _createClass(CommunityShareExceptionDialog, [{
            key: 'hasInitialCommunityTypeStatesChanged',
            value: function hasInitialCommunityTypeStatesChanged() {
                var hasChanged = false;
                var cts = this[_initialCommunityTypeState];
                $('.community-share-exception-checkbox', $(this[_getModalJQuerySelector]())).each(function (_, elem) {
                    var checkBox = $(elem);
                    hasChanged = cts.some(function (cts) {
                        return cts.communityType === checkBox.data('community-type') && cts.isUnchecked !== checkBox.hasClass('box_unchecked');
                    });

                    if (hasChanged) {
                        return false;
                    }
                });

                return hasChanged;
            }
        }, {
            key: _getExcludedCommunityTypes,
            value: function value() {
                var communityShareTypes = [];
                var affiliationShareTypes = [];
                $('.community-share-exception-checkbox[data-community-type]', $(this[_getModalJQuerySelector]())).each(function () {
                    var $checkBox = $(this);
                    var communityType = $checkBox.data('community-type');
                    if (communityType == affiliationCommunityType && $checkBox.is('.unbox_checked, .box_partial_checked')) {
                        var affiliationShareType = {
                            communityType: communityType
                        };
                        affiliationShareTypes.push(affiliationShareType);
                    } else if ($checkBox.hasClass('box_unchecked')) {
                        var communityShareType = {
                            communityType: communityType
                        };
                        communityShareTypes.push(communityShareType);
                    }
                });

                return {
                    communityShareTypes: communityShareTypes,
                    affiliationShareTypes: affiliationShareTypes
                };
            }
        }, {
            key: _updateCommunityShareExceptionPopover,
            value: function value(sku, appUserId) {
                var url = 'products/' + sku + '/users/' + appUserId + '/communities/shares-exceptions/display';
                $('.community-exceptions-list-' + sku + '-' + appUserId).load(url, function () {
                    var numberOfRows = $('table tr', $(this)).length;
                    $('#community-share-exception-count-' + $(this).data('review-id')).html(numberOfRows);
                });
            }
        }, {
            key: _getModalJQuerySelector,
            value: function value() {
                return '#community-share-rows';
            }
        }, {
            key: _initializeCommunityTypeState,
            value: function value() {
                this[_initialCommunityTypeState] = [];
                var cts = this[_initialCommunityTypeState];
                $('.community-share-exception-checkbox', $(this[_getModalJQuerySelector]())).each(function () {
                    var $checkBox = $(this);
                    var communityShareType = {
                        communityType: $checkBox.data('community-type'),
                        isUnchecked: $checkBox.hasClass('box_unchecked')
                    };

                    cts.push(communityShareType);
                });
            }
        }, {
            key: _doSaveAsDefault,
            value: function value() {
                return $('#community-share-save-as-default').hasClass('box_checked');
            }
        }, {
            key: 'save',
            value: function save(sku, appUserId, callback) {
                var _this4 = this;

                var excludedCommunityTypes = this[_getExcludedCommunityTypes]();

                ePlus.modules.community.api.exception.updateCommunityShareExceptions('PUT', $('#community-share-type-id').attr('data-share-type-id'), $('#community-share-app-user-id').attr('data-app-user-id'), this[_doSaveAsDefault](), excludedCommunityTypes.communityShareTypes).done(function () {
                    _this4[_updateAffiliationExceptions](sku, appUserId).done(function () {
                        ePlus.modules.community.api.exception.getCommunityReviewShareCount(sku).done(function (reviewShareCount) {
                            $('#community-share-count').html(reviewShareCount.toLocaleString(ePlus.user.culture));

                            if (typeof callback === 'function') {
                                callback();
                            }
                        }).fail(function () {
                            $('#community-share-count').html('-');
                        });

                        _this4[_updateCommunityShareExceptionPopover](sku, appUserId);
                    });
                }).fail(function () {
                    alert(getRes('error_unexpected'));
                });
            }
        }, {
            key: 'initializeInlineCommunityShareExceptions',
            value: function initializeInlineCommunityShareExceptions(sku, appUserId) {
                var _this5 = this;

                $('.community-share-exception-checkbox', $(this[_getModalJQuerySelector]())).on('click', function () {
                    var $this = $(this);
                    window.enableSingleCheckbox($this);

                    if ($this.data('community-type') === affiliationCommunityType) {
                        affiliationTypeStateOverrides = [];
                    }
                });

                $('#affiliation-share-exception').on('click', function () {
                    _this5[_affiliationShareExceptionDialog].openAffiliationExceptionModal(sku, appUserId);
                });
            }
        }, {
            key: 'initializeCommunityShareException',
            value: function initializeCommunityShareException(sku, appUserId) {
                var _this6 = this;

                var url = '/products/' + sku + '/users/' + appUserId + '/communities/shares-exceptions';
                openMultiModal({
                    id: this[_modalId],
                    url: url,
                    width: '300px',
                    height: '275px',
                    isFixup: true,
                    onLoad: function onLoad() {
                        $("#pop-modal-content-CommunityShareExceptionsModal").zIndex($("#reviewTitleTurbo_" + sku).zIndex() + 1);
                        affiliationTypeStateOverrides = [];

                        $('.community-share-exception-checkbox', $(_this6[_getModalJQuerySelector]())).on('click', function () {
                            var $this = $(this);
                            window.enableSingleCheckbox($this);

                            if ($this.data('community-type') === affiliationCommunityType) {
                                affiliationTypeStateOverrides = [];
                            }
                        });

                        $('#community-share-exception-save').on('click', function (event) {
                            var excludedCommunityTypes = _this6[_getExcludedCommunityTypes]();

                            ePlus.modules.community.api.exception.updateCommunityShareExceptions('PUT', $(event.currentTarget).attr('data-share-type-id'), $(event.currentTarget).data('app-user-id'), _this6[_doSaveAsDefault](), excludedCommunityTypes.communityShareTypes).done(function () {
                                _this6[_updateAffiliationExceptions](sku, appUserId).done(function () {
                                    ePlus.modules.community.api.exception.getCommunityReviewShareCount(sku).done(function (reviewShareCount) {
                                        $('#community-share-count').html(reviewShareCount.toLocaleString(ePlus.user.culture));
                                    }).fail(function () {
                                        $('#community-share-count').html('-');
                                    });

                                    _this6[_updateCommunityShareExceptionPopover](sku, appUserId);
                                });

                                closeMultiModal(_this6[_modalId]);
                            }).fail(function () {
                                alert(getRes('error_unexpected'));
                            });
                        });

                        $('#affiliation-share-exception').on('click', function () {
                            _this6[_affiliationShareExceptionDialog].openAffiliationExceptionModal(sku, appUserId);
                        });

                        $("#selectAllCommunityShareExceptions").on("click", function () {
                            $(".community-share-exception-checkbox").not("#community-share-save-as-default").removeClass("box_unchecked").addClass("box_checked");
                        });

                        $("#unselectAllCommunityShareExceptions").on("click", function () {
                            $(".community-share-exception-checkbox").not("#community-share-save-as-default").removeClass("box_checked").addClass("box_unchecked");
                        });

                        _this6[_initializeCommunityTypeState]();
                    },
                    onClose: function onClose() {
                        if (_this6.hasInitialCommunityTypeStatesChanged()) {
                            return confirm(getRes('you_have_unsaved_changes_will_be_lost'));
                        }

                        return true;
                    }
                });
            }
        }, {
            key: _updateAffiliationExceptions,
            value: function value(sku, appUserId) {
                if (this[_affiliationShareExceptionDialog].doSaveAffiliationShareTypes()) {
                    return ePlus.modules.community.api.exception.saveAffiliationShareExceptions(sku, appUserId, this[_doSaveAsDefault](), this[_affiliationShareExceptionDialog].getExcludedAffiliationTypes());
                } else {
                    return ePlus.modules.community.api.exception.deleteAffiliationShareExceptions(sku, appUserId, null);
                }
            }
        }]);

        return CommunityShareExceptionDialog;
    }();

    if (typeof ePlus.modules.community === 'undefined') {
        ePlus.modules.community = {};
    }

    if (typeof ePlus.modules.community.ui === 'undefined') {
        ePlus.modules.community.ui = {};
    }

    if (typeof ePlus.modules.community.ui.dialog === 'undefined') {
        ePlus.modules.community.ui.dialog = {};
    }

    ePlus.modules.community.ui.dialog.exception = new CommunityShareExceptionDialog();
})();

if (!window.ePlus.modules.widgets) {
    window.ePlus.modules.widgets = {};
}

window.ePlus.modules.widgets.createNewLead = {};

window.ePlus.modules.widgets.createNewLead.ui = function () {
    var openCreateNewLead = function openCreateNewLead(startingLeadOption) {
        var url = "/GetTreelineControl.aspx?controlName=/uc/support/leads/createNewLead.ascx";

        if (startingLeadOption) {
            url += '&startingLeadOption=' + startingLeadOption;
        }

        openMultiModal({
            id: 'pipeline-new-lead',
            url: url,
            width: "560px",
            height: "490px"
        });
    };

    return {
        openCreateNewLead: openCreateNewLead
    };
}();;
window.ePlus.modules.manageAffiliationEmail = (function () {
    var res = ePlus.resources;

    var initialReviewEmailAddress;
    var initialSubmissionEmailAddress;
    var initialSubmissionAlias;
    var initialSubmissionFrequency;
    var initialSubmissionDayOfWeek;

    var initialDoSubscribeToReviewEmail = false;
    var initialDoSubscribeToSubmissionEmail = false;

    var existingReviewSubscriptionId;
    var existingSubmissionSubscriptionId;

    var DAILY_FREQUENCY = "Daily";
    var WEEKLY_FREQUENCY = "Weekly";
    var MONTHLY_FREQUENCY = "Monthly";

    var DAILY_DAYS_BACK = 1;
    var WEEKLY_DAYS_BACK = 7;
    var MONTHLY_DAYS_BACK = 30;

    var DEFAULT_DAYS_BACK = WEEKLY_DAYS_BACK;
    var DEFAULT_REPORT_FREQUENCY = WEEKLY_FREQUENCY;
    var DAY_OF_WEEK = 2;

    var reviewSubscriptionType = getEnumValue('UserAffiliationEmailType', 'REVIEWDIGEST');
    var submissionSubscriptionType = getEnumValue('UserAffiliationEmailType', 'SUBMISSIONDIGEST');

    var getValidReviewEmailsFromDelimitedField = function () {
        return getValidEmailsFromDelimitedField(getReviewEmailAddress());
    }

    var getValidSubmissionEmailsFromDelimitedField = function () {
        return getValidEmailsFromDelimitedField(getSubmissionEmailAddress());
    }

    var getValidEmailsFromDelimitedField = function (emailAddress) {
        if (emailAddress === '') return '';

        var emails = [];
        var emailAddresses = emailAddress.split(/[,;\s]+/);
        emailAddresses.forEach(function (email) {
            if (isValidEmail(email)) {
                emails.push(email);
            }
        });

        return emails.join(',');
    }

    var initSubscription = function (reviewSubscriptionId, submissionSubscriptionId) {
        if (reviewSubscriptionId > 0) {
            initialDoSubscribeToReviewEmail = true;
            existingReviewSubscriptionId = reviewSubscriptionId;

            $.ajax({
                type: 'GET',
                url: '/api/subscriptions/' + reviewSubscriptionId
            })
                .done(function (subscriptionDTO) {
                    initialReviewEmailAddress = subscriptionDTO ? subscriptionDTO.recipientEmail : '';
                    setReviewEmailAddress(initialReviewEmailAddress);

                    getReviewSubscriptionRunDates(reviewSubscriptionId);
                })
                .fail(function () {
                    initialReviewEmailAddress = '';
                });
        } else {
            initialDoSubscribeToReviewEmail = false;
            initialReviewEmailAddress = '';
            existingReviewSubscriptionId = 0;
        }

        if (submissionSubscriptionId > 0) {
            initialDoSubscribeToSubmissionEmail = true;
            existingSubmissionSubscriptionId = submissionSubscriptionId;

            $.ajax({
                type: 'GET',
                url: '/api/subscriptions/' + submissionSubscriptionId
            })
                .done(function (subscriptionDTO) {
                    initialSubmissionEmailAddress = subscriptionDTO ? subscriptionDTO.recipientEmail : '';
                    initialSubmissionFrequency = subscriptionDTO ? subscriptionDTO.frequency : getSubmissionFrequency();
                    initialSubmissionDayOfWeek = subscriptionDTO ? subscriptionDTO.dayOfWeek : getSubmissionDayOfWeek();

                    setSubmissionFrequency(initialSubmissionFrequency, initialSubmissionDayOfWeek);
                    setSubmissionEmailAddress(initialSubmissionEmailAddress);
                    getSubmissionSubscriptionRunDates(submissionSubscriptionId);
                })
                .fail(function () {
                    initialSubmissionEmailAddress = '';
                });
        } else {
            initialDoSubscribeToSubmissionEmail = false;
            initialSubmissionEmailAddress = '';
            initialSubmissionFrequency = getSubmissionFrequency();
            initialSubmissionDayOfWeek = getSubmissionDayOfWeek();
            existingSubmissionSubscriptionId = 0;
        }

        initialSubmissionAlias = $('#submission-alias').val();
    }

    var createReviewSubscription = function (affiliationId, emailAddress) {
        var uri = window.location.origin
            + '/api/affiliations/'
            + affiliationId
            + '/reviews/report?daysBack=' + DEFAULT_DAYS_BACK
            + '&cultureName=' + window.cultureName;

        var subscription = {
            uri: uri,
            frequency: DEFAULT_REPORT_FREQUENCY,
            dayOfWeek: DAY_OF_WEEK,
            recipientEmail: emailAddress,
            attachDataToWorkbook: true,
            subscriptionID: existingReviewSubscriptionId,
            isDataRequiredForReport: true,
            tag: 'community.subscription.review',
            messageSubject: getReviewSubject(),
            subscriptionName: getReviewName()
        };

        return createSubscription(affiliationId, reviewSubscriptionType, subscription);
    }

    var createSubmissionSubscription = function (affiliationId, emailAddress) {
        var frequency = $('#submission-frequency').find(':selected').val();
        var daysBack = DAILY_DAYS_BACK;

        if (frequency === WEEKLY_FREQUENCY) {
            daysBack = WEEKLY_DAYS_BACK;
        } else if (frequency === MONTHLY_FREQUENCY) {
            daysBack = MONTHLY_DAYS_BACK;
        }

        var uri = window.location.origin
            + '/api/affiliations/'
            + affiliationId
            + '/submissions/report?daysBack=' + daysBack
            + '&cultureName=' + window.cultureName;

        var subscription = {
            uri: uri,
            frequency: frequency,
            dayOfWeek: $('#submission-day-of-week').find(':selected').val(),
            recipientEmail: emailAddress,
            attachDataToWorkbook: true,
            subscriptionID: existingSubmissionSubscriptionId,
            isDataRequiredForReport: true,
            tag: 'community.subscription.submission',
            messageSubject: getSubmissionSubject(),
            subscriptionName: getSubmissionAlias()
        };

        return createSubscription(affiliationId, submissionSubscriptionType, subscription);
    }

    var createSubscription = function (affiliationId, type, subscription) {
        return new Promise(function (resolve, reject) {
            $.ajax({
                type: 'POST',
                url: 'api/subscriptions/' + type + '/affiliations/' + affiliationId,
                data: subscription,
            })
                .done(function (subscriptionId) {
                    resolve(subscriptionId);
                })
                .fail(function (message) {
                    reject(message);
                });
        });
    };

    var saveAffiliationAlias = function (affiliationId, type, alias) {
        $.ajax({
            type: 'PUT',
            url: '/api/affiliations/' + affiliationId + '/alias/' + type,
            data: { '': alias },
            dataType: 'application/json'
        });
    }

    var deleteSubscription = function (subscriptionId) {
        return new Promise(function (resolve, reject) {
            $.ajax({
                type: 'DELETE',
                url: '/api/subscriptions/' + subscriptionId
            })
                .done(function () {
                    resolve();
                })
                .fail(function () {
                    reject();
                });
        });
    };

    var deleteUserAffiliationReviewSubscriptionId = function (affiliationId, subscriptionId) {
        deleteUserAffiliationSubscriptionId(affiliationId, subscriptionId, reviewSubscriptionType);
    }

    var deleteUserAffiliationSubmissionSubscriptionId = function (affiliationId, subscriptionId) {
        deleteUserAffiliationSubscriptionId(affiliationId, subscriptionId, submissionSubscriptionType);
    }

    var deleteUserAffiliationSubscriptionId = function (affiliationId, subscriptionId, type) {
        $.ajax({
            type: 'DELETE',
            url: '/api/affiliations/' + affiliationId + '/subscriptions/' + type + '/' + subscriptionId
        })
            .done(function () {
                alert(res.getRes('email_subscription_deleted'))
            })
            .fail(function () {
                alert(res.getRes('error_unexpected'));
            });
    };

    var getReviewEmailAddress = function () {
        return $(getReviewEmailAddressJquerySelector()).val();
    }

    var getReviewName = function () {
        return $(getReviewNameJquerySelector()).val();
    }

    var getReviewSubject = function () {
        return $(getReviewSubjectJquerySelector()).val();
    }

    var getSubmissionEmailAddress = function () {
        return $(getSubmissionEmailAddressJquerySelector()).val();
    }

    var getSubmissionSubject = function () {
        return getSubmissionAlias() + ' ' + res.getRes('review_submissions');
    }

    var getSubmissionAlias = function () {
        return $(getSubmissionAliasJquerySelector()).val().trim();
    }

    var getSubmissionFrequency = function () {
        return $(getSubmissionFrequencyJquerySelector).find(':selected').val();
    }

    var getSubmissionDayOfWeek = function () {
        return parseInt($(getSubmissionDayOfWeekJquerySelector()).find(':selected').val());
    }

    var getReviewEmailAddressJquerySelector = function () {
        return '#review-email';
    }

    var getReviewNameJquerySelector = function () {
        return '#review-name';
    }

    var getReviewSubjectJquerySelector = function () {
        return '#review-subject';
    }

    var getSubmissionEmailAddressJquerySelector = function () {
        return '#submission-email';
    }

    var getSubmissionAliasJquerySelector = function () {
        return '#submission-alias';
    }

    var getSubmissionFrequencyJquerySelector = function () {
        return '#submission-frequency';
    }

    var getSubmissionDayOfWeekJquerySelector = function () {
        return '#submission-day-of-week';
    }

    var getReviewSubscriptionRunDates = function (subscriptionId) {
        $.ajax({
            type: 'GET',
            url: 'api/subscriptions/' + subscriptionId + '/runs/dates'
        })
            .done(function (response) {
                setReviewPreviousRunDate(response.previousRun);
                setReviewNextRunDate(response.nextRun);
            });
    }

    var getSubmissionSubscriptionRunDates = function (subscriptionId) {
        $.ajax({
            type: 'GET',
            url: 'api/subscriptions/' + subscriptionId + '/runs/dates'
        })
            .done(function (response) {
                setSubmissionPreviousRunDate(response.previousRun);
                setSubmissionNextRunDate(response.nextRun);
            });
    }

    var hasReviewUnsavedChanges = function () {
        return initialDoSubscribeToReviewEmail !== $('#review-subscribe-toggle').is(':checked')
            || initialReviewEmailAddress.trim().toLowerCase() !== getReviewEmailAddress().trim().toLowerCase();
    }

    var hasSubmissionUnsavedChanges = function () {
        return initialDoSubscribeToSubmissionEmail !== $('#submission-subscribe-toggle').is(':checked')
            || initialSubmissionEmailAddress.trim().toLowerCase() !== getSubmissionEmailAddress().trim().toLowerCase()
            || initialSubmissionAlias.trim().toLowerCase() !== getSubmissionAlias().trim().toLowerCase()
            || initialSubmissionFrequency !== getSubmissionFrequency()
            || initialSubmissionDayOfWeek !== getSubmissionDayOfWeek();
    }

    var hasUnsavedChanges = function () {
        return hasReviewUnsavedChanges();
    }

    var setReviewEmailAddress = function (emailAddress) {
        $(getReviewEmailAddressJquerySelector()).val(emailAddress);
    }

    var setSubmissionEmailAddress = function (emailAddress) {
        $(getSubmissionEmailAddressJquerySelector()).val(emailAddress);
    }

    var setSubmissionFrequency = function (frequency, dayOfWeek) {
        $('#submission-frequency-con').removeClass('hidden');
        $('#submission-frequency').val(frequency);
        $('#submission-day-of-week').val(dayOfWeek);

        if (frequency === 'Weekly') {
            $('#submission-day-of-week').removeClass('hidden');
        }
    }

    var updateSubscriptions = function (affiliationId, reviewSubscriptionId, submissionSubscriptionId) {
        if (!hasReviewUnsavedChanges() && !hasSubmissionUnsavedChanges()) {
            alert(res.getRes('there_are_no_changes_to_save'));
            return;
        }

        updateReviewSubscription(affiliationId, existingReviewSubscriptionId);
    }

    var updateReviewSubscription = function (affiliationId, reviewSubscriptionId) {
        if (!hasReviewUnsavedChanges()) {
            return;
        }

        if ($('#review-subscribe-toggle').is(':checked')) {
            var emails = getValidReviewEmailsFromDelimitedField();
            if (emails === '') {
                alert(res.getRes('enter_valid_email'));
                return;
            }

            createReviewSubscription(affiliationId, emails)
                .then(function (subscriptionId) {
                    setUserAffiliationReviewSubscriptionId(affiliationId, subscriptionId);
                    updateReviewInitialState();
                    getReviewSubscriptionRunDates(subscriptionId);
                })
                .catch(function (message) {
                    alert(message);
                });
        } else {
            deleteSubscription(reviewSubscriptionId).then(function () {
                existingReviewSubscriptionId = 0;
                deleteUserAffiliationReviewSubscriptionId(affiliationId, reviewSubscriptionId);
                updateReviewInitialState();
                clearReviewRunDates();
            });
        }
    };

    var updateSubmissionSubscription = function (affiliationId, submissionSubscriptionId) {
        if (!hasSubmissionUnsavedChanges()) {
            return;
        }

        if ($('#submission-subscribe-toggle').is(':checked')) {
            var emails = getValidSubmissionEmailsFromDelimitedField();
            if (emails === '') {
                alert(res.getRes('enter_valid_email'));
                return;
            }

            var alias = getSubmissionAlias();
            if (alias === '') {
                alert(res.getRes('enter_valid_name'));
                return;
            }

            createSubmissionSubscription(affiliationId, emails)
                .then(function (subscriptionId) {
                    saveAffiliationAlias(affiliationId, getEnumValue('AffiliationAliasType', 'SUBMISSION'), getSubmissionAlias());
                    setUserAffiliationSubmissionSubscriptionId(affiliationId, subscriptionId);
                    updateSubmissionInitialState();
                    getSubmissionSubscriptionRunDates(subscriptionId);
                })
                .catch(function (message) {
                    alert(message);
                });
        } else {
            deleteSubscription(submissionSubscriptionId).then(function () {
                existingSubmissionSubscriptionId = 0;
                deleteUserAffiliationSubmissionSubscriptionId(affiliationId, submissionSubscriptionId);
                updateSubmissionInitialState();
                clearSubmissionRunDates();
            });
        }
    };

    var setUserAffiliationReviewSubscriptionId = function (affiliationId, subscriptionId) {
        existingReviewSubscriptionId = subscriptionId;
        setUserAffiliationSubscriptionId(affiliationId, subscriptionId, reviewSubscriptionType);
    }


    var setUserAffiliationSubmissionSubscriptionId = function (affiliationId, subscriptionId) {
        existingSubmissionSubscriptionId = subscriptionId;
        setUserAffiliationSubscriptionId(affiliationId, subscriptionId, submissionSubscriptionType);
    }

    var setUserAffiliationSubscriptionId = function (affiliationId, subscriptionId, type) {
        $.ajax({
            type: 'POST',
            url: '/api/affiliations/' + affiliationId + '/subscriptions/' + type + '/' + subscriptionId
        })
            .done(function () {
                alert(res.getRes('email_subscription_successful'));
            })
            .fail(function () {
                alert(res.getRes('error_unexpected'));
            });
    };

    var updateReviewInitialState = function () {
        initialDoSubscribeToReviewEmail = $('#review-subscribe-toggle').is(':checked');
        initialReviewEmailAddress = getReviewEmailAddress();
    }

    var updateSubmissionInitialState = function () {
        initialDoSubscribeToSubmissionEmail = $('#submission-subscribe-toggle').is(':checked');
        initialSubmissionEmailAddress = getSubmissionEmailAddress();
        initialSubmissionAlias = getSubmissionAlias();
        initialSubmissionFrequency = getSubmissionFrequency();
        initialSubmissionDayOfWeek = getSubmissionDayOfWeek();
    }

    var clearReviewRunDates = function () {
        $('#review-subscription-last-run').html('');
        $('#review-subscription-next-run').html('');
    }

    var clearSubmissionRunDates = function () {
        $('#submission-subscription-last-run').html('');
        $('#submission-subscription-next-run').html('');
    }

    var setPreviousRunDate = function (previousRun, jquerySelector) {
        var previousRunDateText = res.getRes('digest_has_never_been_generated');

        if (previousRun) {
            var localizedPreviousRunDate = new Date(previousRun).toLocaleDateString(ePlus.user.culture, 'MMDDYYYY');
            previousRunDateText = res.getRes('the_last_digest_was_run_on').replace('{0}', localizedPreviousRunDate);
        }

        $(jquerySelector).html(previousRunDateText);
    }

    var setReviewPreviousRunDate = function (previousRun) {
        setPreviousRunDate(previousRun, '#review-subscription-last-run');
    }

    var setSubmissionPreviousRunDate = function (previousRun) {
        setPreviousRunDate(previousRun, '#submission-subscription-last-run');
    }

    var setNextRunDate = function (nextRun, jquerySelector) {
        var localizedNextRunDate = new Date(nextRun).toLocaleDateString(ePlus.user.culture, 'MMDDYYYY');
        var nextRunDateText = res.getRes('the_next_digest_will_run_on').replace('{0}', localizedNextRunDate);
        $(jquerySelector).html(nextRunDateText);
    }

    var setReviewNextRunDate = function (nextRun) {
        setNextRunDate(nextRun, '#review-subscription-next-run');
    }

    var setSubmissionNextRunDate = function (nextRun) {
        setNextRunDate(nextRun, '#submission-subscription-next-run');
    }

    return {
        hasReviewUnsavedChanges: hasReviewUnsavedChanges,
        hasSubmissionUnsavedChanges: hasSubmissionUnsavedChanges,
        hasUnsavedChanges: hasUnsavedChanges,
        updateReviewSubscription: updateReviewSubscription,
        updateSubmissionSubscription: updateSubmissionSubscription,
        updateSubscriptions: updateSubscriptions,
        initSubscription: initSubscription
    }

})();;
;

window.ePlus.modules.manageUserAffiliation.requirements = (function () {
    var util = window.ePlus.util;
    var requirements = [];
    var pendingRequirements = [];

    var addAccount = function (item) {
        var type = util.getEnumValue("groupValidation", "ORGANIZATION");
        if (doesRequirementAlreadyExist(type, item.value)) {
            return false;
        }

        addToRequirementList(item.value, item.category);
        pendingRequirements.push({ type: type, value: item.value, label: item.category });
    };

    var addAffiliation = function (item) {
        var type = util.getEnumValue("groupValidation", "GROUP");
        if (doesRequirementAlreadyExist(type, item.value)) {
            return false;
        }

        addToRequirementList(item.value, item.label);
        pendingRequirements.push({ type: type, value: item.value, label: item.label });
    };

    var addEmails = function (textAreaEmails) {
        clearPendingRequirements();
        if (_.isEmpty(textAreaEmails.trim())) return;
        var emails = textAreaEmails.split(/[,;\s]+/);

        if (emails.length === 0) return;

        var invalidEmails = [];
        var type = getEnumValue("groupValidation", "EMAIL");
        emails.forEach(function (email) {
            if (isValidEmail(email)) {
                if (!doesRequirementAlreadyExist(type, email)) {
                    pendingRequirements.push({ type: type, value: email, label: email });
                }
            } else {
                if (!_.isEmpty(email.trim())) {
                    invalidEmails.push(email);
                }
            }
        });

        if (invalidEmails.length > 0) {
            alert(buildAlertMessage(invalidEmails));
        }
    };

    var addToRequirementList = function (key, label) {
        var $requirementsTable = $('#requirementsTable');
        var removeButton = '<span id="remove-button-' + key + '" class="icon-x clickable margin-top-2" data-key="' + key + '"></span>';
        $('tbody', $requirementsTable).append('<tr><td class="flex-container flex-space-between-children pad-left-10 pad-right-10">' 
            + label + removeButton + '</td></tr>');

        $('#remove-button-' + key, $requirementsTable).off().on('click', function(e) {
            window.ePlus.modules.manageUserAffiliation.requirements.removeRow(this);
        });
    };

    function buildAlertMessage(invalidEmails) {
        var message = window.getRes('the_following_were_invalid_emails_and_will_be_ignored') + '\n';
        invalidEmails.forEach(function (email) {
            message += email + '\n';
        });

        return message;
    }

    var clearPendingRequirements = function () {
        pendingRequirements = [];
    }

    var commitPendingRequirements = function () {
        requirements = pendingRequirements;
        window.ePlus.modules.manageUserAffiliation.setUnsavedChanges(true);
    };

    var getPendingRequirements = function () {
        return pendingRequirements;
    };

    var getRequirements = function () {
        return requirements;
    };

    var initAutoComplete = function (autoCompleteId, values, resultCallback) {
        $('#' + autoCompleteId).autocomplete(values, {
            matchContains: true,
            max: 500,
            width: 350,
            formatItem: function (item) {
                return item.label;
            }
        }).result(function (_e, item) {
            if (item) {
                if (typeof resultCallback === 'function') {
                    resultCallback(item);
                }

                $('#requirementsAutoComplete').val('');
            }
        });
    };

    var openAutoCompleteModal = function (affiliationId, values, type) {
        setRequirements(type);
        var url = '/GetTreelineControl.aspx?controlName=/uc/affiliations/UserAffiliationAutoCompleteRequirements.ascx&affiliationId=' + affiliationId + '&type=' + type;
        openChildModal(url, '500px', '450px', function () {
            var callback;
            switch (type) {
                case getEnumValue('groupValidation', 'ORGANIZATION'):
                    callback = window.ePlus.modules.manageUserAffiliation.requirements.addAccount;
                    break;
                case getEnumValue('groupValidation', 'GROUP'):
                    callback = window.ePlus.modules.manageUserAffiliation.requirements.addAffiliation;
                    break;
            }
            window.ePlus.modules.manageUserAffiliation.requirements.initAutoComplete('requirementsAutoComplete', values, callback);
        });
    };

    var openEmailModal = function (affiliationId, values, type) {
        setRequirements(type);
        var url = '/GetTreelineControl.aspx?controlName=/uc/affiliations/UserAffiliationEmailRequirements.ascx&affiliationId=' + affiliationId + '&type=' + type;
        openChildModal(url, '500px', '500px', null, null, { emails: JSON.stringify(values) });
    }

    var removeRow = function (self) {
        var $row = $(self);
        var key = $row.data('key');

        pendingRequirements = _.filter(pendingRequirements, function (req) {
            return req.value != key;
        });

        $row.closest('tr').remove();
    };

    function doesRequirementAlreadyExist(type, value) {
        var reqs = _.filter(pendingRequirements, function (r) {
            return r.type === type && r.value === value;
        });

        return reqs && reqs.length > 0;
    }

    function setRequirements (type) {
        var currentRequirementsByType = ePlus.modules.manageUserAffiliation.getRequirementsByType(type);

        pendingRequirements = (currentRequirementsByType && currentRequirementsByType.length > 0) ? currentRequirementsByType : [];
        requirements = [];
    }

    return {
        addAccount: addAccount,
        addAffiliation: addAffiliation,
        addEmails: addEmails,
        addToRequirementList: addToRequirementList,
        clearPendingRequirements: clearPendingRequirements,
        commitPendingRequirements: commitPendingRequirements,
        getPendingRequirements: getPendingRequirements,
        getRequirements: getRequirements,
        initAutoComplete: initAutoComplete,
        openAutoCompleteModal: openAutoCompleteModal,
        openEmailModal: openEmailModal,
        removeRow: removeRow
    }
})();
;
window.ePlus.modules.manageUserAffiliation.filter = (function () {
    var isNewFilterDefinition;
    var initialFilterDefinition;
    var filterDefinition;
    var res = ePlus.resources;
    var util = ePlus.util;

    var affiliationFilterCallback = function (groupId, callback) {
        if (isNewFilterDefinition && hasFilterDefinition()) {
            createGroupBrowseView(groupId, callback);
        } else if (!isNewFilterDefinition && hasFilterDefinition()) {
            updateGroupBrowseView(groupId, callback);
        } else if (!isNewFilterDefinition && !hasFilterDefinition()) {
            deleteGroupBrowseView(groupId, callback);
        } else {
            callback();
        }
    };

    var changeCategoryType = function (option) {
        var itemType = option.attr("val");
        var url = null;
        switch (itemType) {
            case util.getEnumValue('browseByType', 'TITLEGERMANFORMAT'):
            case util.getEnumValue('browseByType', 'TITLEBISACSUBJECT'):
            case util.getEnumValue('browseByType', 'TITLEDEWEYCLASS'):
            case util.getEnumValue('browseByType', 'TITLEBIC1'):
            case util.getEnumValue('browseByType', 'CUSTOMCOLLECTION1'):
            case util.getEnumValue('browseByType', 'CUSTOMCOLLECTION2'):
            case util.getEnumValue('browseByType', 'CUSTOMCOLLECTION3'):
            case util.getEnumValue('browseByType', 'CUSTOMCOLLECTION4'):
            case util.getEnumValue('browseByType', 'CUSTOMCOLLECTION5'):
            case util.getEnumValue('browseByType', 'CUSTOMCOLLECTION6'):
            case util.getEnumValue('browseByType', 'TITLECUSTOMCATEGORY1'):
            case util.getEnumValue('browseByType', 'TITLECUSTOMCATEGORY2'):
                url = "/GetTreelineControl.aspx?controlName=/uc/search/searchOptionTree.ascx&itemType=" + itemType;
                break;
            case util.getEnumValue('browseByType','TITLEPUBDATE'):
                url = "/GetTreelineControl.aspx?controlName=/uc/search/searchPubDateOptions.ascx";
                break;
            case util.getEnumValue('browseByType','TITLEVISIBLEDRC'):
                url = "/GetTreelineControl.aspx?controlName=/uc/search/searchGalleyOptions.ascx";
                break;
            default:
                url = "/GetTreelineControl.aspx?controlName=/uc/search/searchOptionScroller.ascx&itemType=" + itemType;
        }
        $("#filterCategoryDiv").load(url);
    };

    function changeDropDownSelection(elem, parent, val, html) {
        $('.selectableFilterOption', '#categoryList').removeClass('hidden');
        $('.' + parent).show();
        $('#' + parent).attr('val', val);
        $('#' + parent).html(html);
        $(elem).hide();
        $('.dropDownOptions').hide();

        if (parent === 'pubRange' || parent === 'pubStart') {
            loadPubDateOptions();
        }
        if (parent === "categoryType") {
            changeCategoryType($(elem));
            // if custom category is selected in the dropdown attribute list, show the warning message
            if (EdelweissAnalytics.customCategoryTypes.indexOf(val) >= 0) {
                switchToCustomCategoryMode();
            } else {
                // Custom category not selected in the dropdown attribute list but in the selected filter, show the warning message
                var hasCustomTerm = false;
                for (i = 0; i < EdelweissAnalytics.customCategoryTypes.length; i++) {
                    if ($("#filter_Row_" + EdelweissAnalytics.customCategoryTypes[i]).is(':visible')) {
                        hasCustomTerm = true;
                        break;
                    }
                }
                if (hasCustomTerm) {
                    switchToCustomCategoryMode();
                } else {
                    switchToGoToListMode();
                }
            }
        }
        if (parent === "savedFilter") {
            loadSavedFilter(val);
        }
    }

    var createGroupBrowseView = function (groupId, callback) {
        var groupBrowseView = {
            groupId: groupId,
            definition: JSON.stringify(getFilterDefinition())
        };

        $.ajax({
            type: 'POST',
            url: '/api/affiliations/' + groupId + '/filters',
            data: groupBrowseView
        })
            .done(function () {
                alert(res.getRes('changes_saved'));

                if (typeof callback === 'function') {
                    callback();
                }
            })
            .error(function () {
                res.getRes('error_unexpected');
            });
    };

    var deleteGroupBrowseView = function (groupId, callback) {
        $.ajax({
            type: 'DELETE',
            url: '/api/affiliations/' + groupId + '/filters'
        })
            .done(function () {
                if (typeof callback === 'function') {
                    callback();
                }

                alert(res.getRes('changes_saved'));
            })
            .error(function () {
                alert(res.getRes('error_unexpected'));
            });
    };

    function filterByPubDate(itemId, itemDisplay) {
        if (itemId === "all") {
            if ($("#filter_Holder_pubDate").html() !== "") {
                //Check if there is a current pubDate filter set -- remove it if so
                removePubDateFilter();
            }
        } else {
            if ($("#filter_Holder_pubDate").html() !== "") {
                //Check if there is a current pubDate filter set -- remove it if so
                $("#pubDate").remove();
            }
            $("#selected_Filters").append("<input type='hidden' id='pubDate' name='pubDate' value='" + itemId + "' />");
            var displayText = "<div class='column' id='fd_pubDate'><div class='columnSpaced icon-close-icon closeFilter fColor' itemType='pubDate' refineVal='pubDate' onclick='javascript:removePubDateFilter();'></div><div class='textMedium selectedFilter column pubDate'>";
            displayText += "<div class='column textMedium' style='margin-right:6px;margin-left:4px;'>" + itemDisplay + "</div></div></div>";
            if ($("#activeFilters_pubDate").val() === "0") {
                $("#activeFilters").val($("#activeFilters").val() * 1 + 1);
                $("#activeFilters_pubDate").val($("#activeFilters_pubDate").val() * 1 + 1);
            }
            $("#filter_Holder_pubDate").html("");
            $("#filter_Holder_pubDate").append(displayText);
            $("#filter_Row_pubDate").show();
            if ($("#activeFilters").val() > 0) {
                $("#noFilterMessage").hide();
            }
        }
    }

    var getFilterDefinition = function () {
        return filterDefinition;
    }

    var hasFilterDefinition = function () {
        return !_.isEmpty(filterDefinition);
    };

    function loadPubDateOptions() {
        $(".pubDateFilter").addClass("box_unchecked");
        $(".pubDateFilter").removeClass("box_checked");
        $("#pd_Custom").addClass("box_checked");
        $("#pd_Custom").removeClass("box_unchecked");
        var pubId = $("#pubStart").attr("val") + "|" + $("#pubRange").attr("val");
        var pubDisplay = 'starting&nbsp;' + $("#pubStart").html() + ', looking out&nbsp;' + $("#pubRange").html();
        filterByPubDate(pubId, pubDisplay);
    }

    function loadSavedFilter(affiliationId) {
        $("#selectedFilter").val(affiliationId);
        if (affiliationId > 0) {
            $.url = "/GetTreelineControl.aspx?controlName=/uc/filters/SavedFilterDisplay.ascx&groupId=" + affiliationId;
            $("#filterDisplay").load($.url, { initialFilterDefinition: JSON.stringify(initialFilterDefinition) }, function () {
                switchToGoToListMode();
                getResultCount();
                setFilterDefinition();
            });
        } else {
            $.url = "/GetTreelineControl.aspx?controlName=/uc/filters/SavedFilterDisplay.ascx&groupId=0";
            $("#filterDisplay").load($.url, { initialFilterDefinition: JSON.stringify(initialFilterDefinition) }, function () {
                setFilterDefinition();
            });
        }
    }

    var openFilterModal = function (affiliationId) {
        filterDefinition = null;

        var onClose = function () {
            if ($("#filterDisplay").find(".selectedFilter").length > 0) {
                window.ePlus.modules.manageUserAffiliation.setUnsavedChangesChild("title", true);
            }           

            var doShowUnsavedChangesChildWarning = window.ePlus.modules.manageUserAffiliation.doShowUnsavedChangesChildWarning("title");
            if (doShowUnsavedChangesChildWarning && !confirm(getRes("you_have_unsaved_changes_will_be_lost"))) {
                return false;
            }

            return true;
        }

        var url = '/GetTreelineControl.aspx?controlName=/uc/affiliations/ManageUserAffiliationFilter.ascx';
        openChildModal(url, '920px', '500px', function () {
            loadSavedFilter(affiliationId);
            $('.selectableFilterOption').on('click', function () {
                var $this = $(this);
                changeDropDownSelection(this, $this.attr('parent'), $this.attr('val'), $this.html());
            });

            $('#cancel-button').on('click', function () {
                filterDefinition = initialFilterDefinition;
                closeModal();
            });

            $('#okay-button').on('click', function () {
                if (initialFilterDefinition !== filterDefinition) {
                    initialFilterDefinition = filterDefinition;
                    ePlus.modules.manageUserAffiliation.setUnsavedChangesChild("title", false);
                    ePlus.modules.manageUserAffiliation.setUnsavedChanges(true);
                }
                updateTitleFilterCount();
                closeModal();
            });
        }, onClose);
    };

    var setFilterDefinition = function () {
        filterDefinition = $('#selected_Filters :input').serializeJSON();
    };

    var setInitialFilterDefinition = function (groupBrowseViewDefinition) {
        initialFilterDefinition = groupBrowseViewDefinition;
    }

    var setIsNewFilterDefinition = function (isNew) {
        isNewFilterDefinition = isNew;
    }

    var updateGroupBrowseView = function (groupId, callback) {
        var groupBrowseView = {
            definition: JSON.stringify(getFilterDefinition())
        };

        $.ajax({
            type: 'PUT',
            url: '/api/affiliations/' + groupId + '/filters',
            data: groupBrowseView
        })
            .done(function () {
                alert(res.getRes('changes_saved'));

                if (typeof callback === 'function') {
                    callback();
                }
            })
            .error(function () {
                res.getRes('error_unexpected');
            });
    };

    function updateTitleFilterCount() {
        var titleFilterCount = 0;
        if (hasFilterDefinition()) {
            titleFilterCount = Object.keys(filterDefinition).length;
        }

        $('#title-filter-count').html(titleFilterCount);
    }

    return {
        affiliationFilterCallback: affiliationFilterCallback,
        createGroupBrowseView: createGroupBrowseView,
        deleteGroupBrowseView: deleteGroupBrowseView,
        getFilterDefinition: getFilterDefinition,
        hasFilterDefinition: hasFilterDefinition,
        openFilterModal: openFilterModal,
        setFilterDefinition: setFilterDefinition,
        setInitialFilterDefinition: setInitialFilterDefinition,
        setIsNewFilterDefinition: setIsNewFilterDefinition,
        updateGroupBrowseView: updateGroupBrowseView
    }
})();;
;
window.ePlus.modules.manageLegacyAffiliation = (function () {
    var res = window.ePlus.resources;

    var $form;
    var $formButton;

    var savingModalControl = 'popModal_inner';

    var unsavedChanges = false;

    var unsavedChangesChild = { "title": false };

    var closeModalAndReloadPage = function () {
        window.closeModal();
        window.reloadCurrentPage();
    }

    var doShowUnsavedChangesWarning = function () {
        return unsavedChanges;
    };

    var doShowUnsavedChangesChildWarning = function (child) {
        return unsavedChangesChild[child];
    };

    var formSubmitCleanup = function () {
        changeFormButtonDisabledStatus(false);
    };

    var getAffiliation = function () {
        var affiliation = $form.serializeJSON({
            parseNumbers: true,
            parseBooleans: true
        });

        return affiliation;
    };

    var initCreateForm = function () {
        initForm(function () {
            if (!isValidAffiliation()) return;
            var affiliation = getAffiliation();
            window.ePlus.modules.legacyAffiliation.createAffiliation(affiliation, function (affiliation) {
                unsavedChanges = false;
            }, formSubmitCleanup);
        });
    };

    var initEditForm = function () {
        initForm(function () {
            if (!isValidAffiliation()) return;
            var affiliation = getAffiliation();
            window.ePlus.modules.legacyAffiliation.updateAffiliation(affiliation, function (affiliation) {
                unsavedChanges = false;
                alert(res.getRes('changes_saved'));
                closeModalAndReloadPage();
            }, formSubmitCleanup);
        });
    };

    var initTabs = function () {
        $('.tab', $('#affiliation-management-tabs')).on('click', function () {
            var dataTab = $(this).data('tab');
            $(this)
                .addClass('tab-selected')
                .siblings('.tab-selected')
                .removeClass('tab-selected');


            $(dataTab)
                .removeClass('hidden')
                .siblings('div.tab-content')
                .addClass('hidden');
        });
    };

    var setUnsavedChanges = function (status) {
        unsavedChanges = status;
    };

    var setUnsavedChangesChild = function (child, status) {
        unsavedChangesChild[child] = status;
    };

    var initForm = function (onSubmit) {
        $form = $('#create-edit-user-affiliation');
        $formButton = $('#create-user-affiliation-btn');
        changeFormButtonDisabledStatus(false);

        $form.on('submit', function (e) {
            e.preventDefault();
            e.stopPropagation();

            changeFormButtonDisabledStatus(true);

            if (typeof onSubmit === 'function') {
                onSubmit();
            }
            
            return false;
        });
    };

    var isValidAffiliation = function() {
        if (!$form[0].checkValidity()) {
            $form.addClass('was-validated');
            return false;
        }

        return true;
    };

    function changeFormButtonDisabledStatus(doDisable) {
        $formButton.prop('disabled', doDisable);
    }

    return {
        doShowUnsavedChangesWarning: doShowUnsavedChangesWarning,
        doShowUnsavedChangesChildWarning: doShowUnsavedChangesChildWarning,
        getAffiliation: getAffiliation,
        initCreateForm: initCreateForm,
        initEditForm: initEditForm,
        initTabs: initTabs,
        setUnsavedChanges: setUnsavedChanges,
        setUnsavedChangesChild: setUnsavedChangesChild
    }
})();;
window.ePlus.modules.listViewAffiliationRow = (function () {
    var DAYS_BACK_PREFERENCE_PREFIX = 'days';
    var USER_PREF_TYPE_COMMUNITY = 'community';

    var res = window.ePlus.resources;
    var util = window.ePlus.util;    

    var getDaysBackUserPrefName = function (affiliationId) {
        return DAYS_BACK_PREFERENCE_PREFIX + '_' + affiliationId;
    }

    var initUserAffiliationMessage = function (affiliationId) {
        $('.message-affiliation-members', '#as_' + affiliationId).each(function () {
            $(this).off().on('click', function () {
                openMessageAffiliationMembers(affiliationId);
            });
        });
    }

    var initUserAffiliationEdit = function (affiliationId) {
        $('.edit-affiliation', '#as_' + affiliationId).each(function () {
            $(this).off().on('click', function () {
                openManageUserAffiliation(affiliationId);
            });
        });

        $('.edit-legacy-affiliation', '#as_' + affiliationId).each(function () {
            $(this).off().on('click', function () {
                openManageLegacyAffiliation(affiliationId);
            });
        });
    }

    var initUserAffiliationDelete = function (affiliationId) {
        $('.delete-affiliation', '#as_' + affiliationId).each(function () {
            var affiliationId = $(this).data('affiliation-id');
            $(this).off().on('click', function () {
                if (confirm(res.getRes("confirm_delete_affiliation"))) {
                    window.ePlus.modules.userAffiliation.deleteAffiliation(affiliationId, window.reloadCurrentPage);
                }
            });
        });
    }
    var initializeListViewAffiliationRow = function (affiliationId) {
        var $affiliationContainer = getAffiliationContainerJqueryElement(affiliationId);
        $('.affiliation-action', $affiliationContainer).off().on('click', function () {
            if (getListViewProperty('resultType') === getEnumValue('resultType', "ELIGIBLEAFFILIATIONS")) {
                performListViewEligibleAffiliationFunctions($(this));
            } else {
                performListViewJoinedAffiliationFunctions($(this));
            }
        });

        $('.affiliation-check', $affiliationContainer).off().on('click', function () {
            var itemId = $(this).data('affiliation-id');
            var rowIndex = _.findIndex(window.rows, function (r) {
                return r.item == itemId;
            });

            window.ePlus.modules.listView.toggleCheck(itemId, rowIndex);
        });

        bindAffiliationActivityCalendarWebuiPopover(affiliationId);
        initAffiliationReviewsCount(affiliationId);
        initAffiliationCatalogsCount(affiliationId);

        if (isViewingJoinedAffiliations()) {
            bindAffiliationReviewsCountModal(affiliationId);
            bindAffiliationCatalogsCountModal(affiliationId);
        }

        initUserAffiliationMessage(affiliationId);
        initUserAffiliationEdit(affiliationId);
        initUserAffiliationDelete(affiliationId);
    };

    var getAffiliationContainerJqueryElement = function (affiliationId) {
        return $('#as_' + affiliationId);
    };

    var getCalendarDaysBack = function (affiliationId) {
        return parseInt($('#affiliation-time-selector-' + affiliationId).attr('data-calendar-days-back'));
    };

    var bindAffiliationReviewsCountModal = function (affiliationId) {
        var $affiliationContainer = getAffiliationContainerJqueryElement(affiliationId);
        var $reviewsCount = $('.num-reviews-shared-with-affiliation', $affiliationContainer);
        
        $reviewsCount.off().on("click", function () {
            var reviewsCreatedDateMaxDaysBack = getCalendarDaysBack(affiliationId);
            window.openAffiliationReviewsModal(affiliationId, reviewsCreatedDateMaxDaysBack);
        });
    };

    var initAffiliationReviewsCount = function (affiliationId) {
        var reviewsCreatedDateMaxDaysBack = getCalendarDaysBack(affiliationId);
        var $affiliationContainer = getAffiliationContainerJqueryElement(affiliationId);
        var $reviewsCount = $('.num-reviews-shared-with-affiliation', $affiliationContainer);
        getAffiliationReviewsCount(affiliationId, reviewsCreatedDateMaxDaysBack).then(function (reviewsCount) {
            $reviewsCount.html(reviewsCount);
        }).catch(function () {
            $reviewsCount.html(0);
        });
    };

    var getAffiliationReviewsCount = function (affiliationId, reviewsCreatedDateMaxDaysBack) {
        var url = '/api/v1/affiliations/' + affiliationId + '/reviews/' + reviewsCreatedDateMaxDaysBack + '/count';
        var reviewsCountPromise = new window.Promise(function (resolve, reject) {
            $.ajax({
                type: 'GET',
                url: url
            }).done(function (reviewsCount) {
                resolve(reviewsCount);
            }).fail(function () {
                reject();
            });
        });
        return reviewsCountPromise;
    };

    var bindAffiliationCatalogsCountModal = function (affiliationId) {
        var $affiliationContainer = getAffiliationContainerJqueryElement(affiliationId);
        var $catalogsCount = $('.num-catalogs-shared-with-affiliation', $affiliationContainer);
        
        $catalogsCount.off().on("click", function () {
            var catalogsCreatedDateMaxDaysBack = getCalendarDaysBack(affiliationId);
            window.openAffiliationCatalogsModal(affiliationId, catalogsCreatedDateMaxDaysBack);
        });
    };

    var initAffiliationCatalogsCount = function (affiliationId) {
        var catalogsCreatedDateMaxDaysBack = getCalendarDaysBack(affiliationId);
        var $affiliationContainer = getAffiliationContainerJqueryElement(affiliationId);
        var $catalogsCount = $('.num-catalogs-shared-with-affiliation', $affiliationContainer);

        getAffiliationCatalogsCount(affiliationId, catalogsCreatedDateMaxDaysBack).then(function (catalogsCount) {
            $catalogsCount.html(catalogsCount);
        }).catch(function () {
            $catalogsCount.html(0);
        });
    };

    var isViewingJoinedAffiliations = function () {
        return getListViewProperty('resultType') === getEnumValue('resultType', "JOINEDAFFILIATIONS");
    }

    var getAffiliationCatalogsCount = function (affiliationId, catalogsCreatedDateMaxDaysBack) {
        var url = '/api/v1/catalogs/communities/' + affiliationId + '/ids/count?daysBack=' + catalogsCreatedDateMaxDaysBack;
        var catalogsCountPromise = new window.Promise(function (resolve, reject) {
            $.ajax({
                type: 'GET',
                url: url
            }).done(function (catalogsCount) {
                resolve(catalogsCount);
            }).fail(function () {
                reject();
            });
        });
        return catalogsCountPromise;
    };

    var selectAffiliationActivityTimeFrame = function (daysBack, affiliationId) {
        SaveUserPreference(USER_PREF_TYPE_COMMUNITY, getDaysBackUserPrefName(affiliationId), daysBack, function () {
            var $elem = $('#affiliation-time-selector-' + affiliationId);
            $elem.webuiPopover('destroy');
            setActivityTitle(affiliationId, daysBack);
            $elem.attr('data-calendar-days-back', daysBack);
            bindAffiliationActivityCalendarWebuiPopover(affiliationId);
            initAffiliationReviewsCount(affiliationId);
        });
    };

    var bindAffiliationActivityCalendarWebuiPopover = function (affiliationId) {
        var $affiliationContainer = getAffiliationContainerJqueryElement(affiliationId);
        var $calendarIcon = $('.icon-calendar', $affiliationContainer);
        var timeSelectorPopoverParams = getCalendarWebuiPopoverParams(affiliationId,
            $calendarIcon.attr('data-calendar-days-back'));

        $calendarIcon.webuiPopover({
            type: 'async',
            cache: false,
            placement: 'bottom-left',
            container: '#pageContent',
            backdrop: true,
            style: 'flex-container',
            url: '/GetTreelineControl.aspx?controlName=/uc/controls/TimeSelectorPop.ascx&' + timeSelectorPopoverParams,
        });
    };

    var getCalendarWebuiPopoverParams = function (affiliationId, daysBack) {
        return $.param({
            resultType: util.getEnumValue('resultType', 'JOINEDAFFILIATIONS'),
            dashboardType: util.getEnumValue('dashType', 'DASH_PEOPlE'),
            dashOption: '0',
            daysBack: daysBack,
            source: 'function',
            function: 'ePlus.modules.listViewAffiliationRow.selectAffiliationActivityTimeFrame',
            functionArgs: JSON.stringify([
                affiliationId
            ])
        });
    };

    var setActivityTitle = function (affiliationId, daysBack) {
        var $activityTitle = $('.activity-title', $('#as_' + affiliationId));
        var url = 'api/affiliations/' + affiliationId + '/activity/' + daysBack;
        $.ajax({
            type: 'GET',
            url: url
        })
            .done(function (label) {
                $activityTitle.html(label);
            });
    }

    var performListViewEligibleAffiliationFunctions = function ($this) {
        var affiliationProps = window.ePlus.modules.listViewAffiliation.getAffiliationDataProperties($this);

        if (isLegacyVerifiedAffiliation(affiliationProps.groupType)) {
            performListViewEligibleLegacyVerifiedAffiliationFunction(affiliationProps);
        } else {
            performListViewEligibleUserAffiliationFunctions(affiliationProps);
        }
    }

    var performListViewEligibleUserAffiliationFunctions = function (affiliationProps) {
        if (affiliationProps.isUserLevel) {
            window.ePlus.modules.listViewAffiliation.joinUserLevelAffiliation(affiliationProps.affiliationId,
                affiliationProps.appUserId,
                ePlus.modules.listViewAffiliation.MEMBER_ROLE_TYPE);
        } else if (confirm(window.getRes('if_you_join_every_user_in_your_organization_will_join_this_group'))) {
            window.ePlus.modules.listViewAffiliation.joinOrgLevelAffiliation(affiliationProps.affiliationId,
                affiliationProps.orgId,
                affiliationProps.orgType);
        }
    }

    var performListViewEligibleLegacyVerifiedAffiliationFunction = function (affiliationProps) {
        if (affiliationProps.isAllAccessAffiliation) {
            window.ePlus.modules.listViewAffiliation.joinAllAccessAffiliation(affiliationProps.affiliationId,
                affiliationProps.appUserId,
                affiliationProps.membershipType
            );
        } else {
            window.ePlus.modules.listViewAffiliation.joinLegacyVerifiedAffiliation(affiliationProps.affiliationId,
                affiliationProps.appUserId,
                affiliationProps.membershipType
            );
        }
    }

    var performListViewJoinedAffiliationFunctions = function ($this) {
        var affiliationProps = window.ePlus.modules.listViewAffiliation.getAffiliationDataProperties($this);

        if (isLegacyVerifiedAffiliation(affiliationProps.groupType)) {
            performListViewJoinedLegacyVerifiedAffiliationFunction(affiliationProps);
        } else {
            performListViewJoinedUserAffiliationFunction(affiliationProps);
        }
    }

    var performListViewJoinedLegacyVerifiedAffiliationFunction = function (affiliationProps) {
        if (affiliationProps.isAllAccessAffiliation) {
            window.ePlus.modules.listViewAffiliation.leaveAllAccessAffiliation(affiliationProps.affiliationId,
                affiliationProps.appUserId,
                affiliationProps.membershipType
            );
        }
    }

    var performListViewJoinedUserAffiliationFunction = function (affiliationProps) {
        if (affiliationProps.isUserLevel) {
            window.ePlus.modules.listViewAffiliation.leaveUserLevelAffiliation(affiliationProps.affiliationId,
                affiliationProps.appUserId,
                ePlus.modules.listViewAffiliation.MEMBER_ROLE_TYPE);
        } else if (confirm(window.getRes('if_you_leave_every_user_in_your_organization_will_leave_this_group'))) {
            window.ePlus.modules.listViewAffiliation.leaveOrgLevelAffiliation(affiliationProps.affiliationId,
                affiliationProps.orgId,
                affiliationProps.orgType);
        }
    }

    var getAffiliationDataProperties = function ($this) {
        return {
            isUserLevel: $this.data('is-user-level'),
            affiliationId: $this.data('affiliation-id'),
            groupType: $this.data('group-type'),
            orgId: $this.data('org-id'),
            userId: $this.data('user-id'),
            orgType: $this.data('org-type')
        }
    }

    var isLegacyVerifiedAffiliation = function (groupType) {
        return groupType === util.getEnumValue('groupType', 'VERIFIEDAFFILIATION');
    }

    var toggleAffiliationVisibility = function (affiliationId, doShowHidden) {
        $('#hide-affiliation-' + affiliationId).on('click', function () {
            if ($(this).hasClass('icon-remove-icon') && !doShowHidden) {
                window.ePlus.modules.listViewAffiliation.decrementEligibleAffiliationCount();
            }

            window.toggleHidden(affiliationId,
                null,
                window.getListViewProperty('resultType'),
                doShowHidden);
        });
    };

    return {
        initializeListViewAffiliationRow: initializeListViewAffiliationRow,
        selectAffiliationActivityTimeFrame: selectAffiliationActivityTimeFrame,
        toggleAffiliationVisibility: toggleAffiliationVisibility
    }
}());
ePlus.modules.categoryAggregates = new (function () {
    var self = this;

    this.aggregateManager = new (function () {
        var _this = this;

        // AggregateManager PUBLIC Methods

        this.initializeDoIncludeInScorecardRunCheckboxClickListener = function (viewId, categoryAggregateName) {
            $(".includeCategoryAggregateInScorecardRunCheckbox[data-viewId='" + viewId + "']").on("click", function () {
                var $this = $(this);
                var doExclude;
                if (ePlus.ui.isBoxChecked($this)) {
                    doExclude = true;
                    ePlus.ui.makeBoxUnchecked($this);
                } else {
                    doExclude = false;
                    ePlus.ui.makeBoxChecked($this);
                }
                _this.updateCategoryAggregate(categoryAggregateName, doExclude);
            });
        };

        this.updateCategoryAggregate = function (categoryAggregateName, doExcludeFromScorecardRuns) {
            var categoryAggregateDto = {
                categoryAggregateName: categoryAggregateName,
                doExcludeFromScorecardRuns: doExcludeFromScorecardRuns
            };
            $.ajax({
                url: "api/v1/categories/aggregate",
                type: "POST",
                contentType: "application/json",
                data: JSON.stringify(categoryAggregateDto),
                error: function () {
                    alert(window.getRes("error_unexpected"));
                }
            });
        };

        this.addCategoryCodeTagsForCategoryAggregate = function (categoryAggregateName, viewId, systemId, tagArrayJson) {
            $("#categoryCodes_" + viewId + "_" + systemId).tag({
                tagClassRoot: 'personalTag',
                allowEdit: true,
                tagArray: tagArrayJson,
                onPreRemoveEventHandler: function (value, callback) {
                    var url = 'api/v1/categories/aggregate/term';
                    var categoryAggregateTermDto = {
                        categoryAggregateName: categoryAggregateName,
                        categoryCode: value,
                        categorySystemId: systemId
                    };
                    $.ajax({
                        type: 'DELETE',
                        url: url,
                        data: JSON.stringify(categoryAggregateTermDto),
                        contentType: 'application/json'
                    }).done(function () {
                        callback(true);
                    }).fail(function () {
                        alert(window.getRes("error_unexpected"));
                    });
                },
                onPostRemoveEventHandler: function () {
                    var numCodesInSystem = getNumCategoryCodesInSystemForCategoryAggregate(viewId, systemId);
                    if (numCodesInSystem === 0) {
                        removeClassificationSystemContainer(viewId, systemId);
                        var numSystemsInCategoryAggregate = getNumClassificationSystemsInCategoryAggregate(viewId);
                        if (numSystemsInCategoryAggregate === 0) {
                            alert(formCategoryAggregatedDeletedMessage(categoryAggregateName));
                            self.attributeListOptions.setCategoryAggregatesCount(getCategoryAggregatesCount() - 1);
                            closeCategoryAggregatesManager();
                        }
                    }
                }
            });
        }; 

        // AggregateManager PRIVATE Methods

        var formCategoryAggregatedDeletedMessage = function (categoryAggregateName) {
            return window.getRes("the_item_type_with_item_name_has_been_deleted")
                .replace("{itemType}", window.getRes("super_category").toLowerCase())
                .replace("{itemName}", categoryAggregateName);
        };

        var getNumCategoryCodesInSystemForCategoryAggregate = function (viewId, systemId) {
            var $systemContainer = getClassificationSystemContainerElement(viewId, systemId);
            return $systemContainer.find(".personalTag-tag").length;
        };

        var getNumClassificationSystemsInCategoryAggregate = function (viewId) {
            var numSystems = $(".orgClassificationSystemContainer[data-viewId='" + viewId + "']").length;
            return numSystems;
        };

        var removeClassificationSystemContainer = function (viewId, systemId) {
            var $systemContainer = getClassificationSystemContainerElement(viewId, systemId);
            $systemContainer.remove();
        };

        var getClassificationSystemContainerElement = function (viewId, systemId) {
            return $(".orgClassificationSystemContainer[data-viewId='" + viewId + "'][data-systemId='" + systemId + "']");
        };

        var closeCategoryAggregatesManager = function () {
            closeMultiModal('categoryAggregates');
        };

        var getCategoryAggregatesCount = function () {
            return parseInt($("#categoryAggregatesCount").html(), 10);
        };
    }); 

    this.attributeList = new (function () {
        var _this = this;

        this.isInTheProcessOfLoadingNextBatch = false;
        this.haveAllBatchesBeenLoaded = false;
        this.$attributeList = null;
        this.$loadingAnimationContainer = null;
        
        // AttributeList PUBLIC Methods //

        this.initialize = function ($attributeList, $loadingAnimationContainer, batchSize, categoryAggregatesAutocomplete) {
            _this.isInTheProcessOfLoadingNextBatch = false;
            _this.haveAllBatchesBeenLoaded = false;
            _this.$attributeList = $attributeList;
            _this.$loadingAnimationContainer = $loadingAnimationContainer;

            $attributeList.scroll(onAttributeListScrollClosure(batchSize, categoryAggregatesAutocomplete));
        }; 

        this.addCategoryAggregateTagsForCode = function (categoryCode, tagArrayJson, categoryAggregateAutocomplete) {
            $("#categoryAggregates_" + CSS.escape(categoryCode)).tag({
                tagClassRoot: 'personalTag',
                allowEdit: true,
                tagAutoCompleteList: categoryAggregateAutocomplete,
                tagArray: tagArrayJson,
                onPreAddEventHandler: function (value, callback) {
                    var url = "api/v1/categories/aggregate/term";
                    var categoryAggregateTermDto = {
                        categoryAggregateName: value,
                        categoryCode: categoryCode,
                        categorySystemId: $("#orgClassification").attr("val")
                    };
                    $.ajax({
                        type: 'POST',
                        url: url,
                        data: JSON.stringify(categoryAggregateTermDto),
                        contentType: 'application/json'
                    }).done(function () {
                        getAllCategoryAggregateNames().then(function (aggregateNames) {
                            var autocomplete = aggregateNames.join("~");
                            window.UpdateQuickTagAutoCompletes(autocomplete, $("input[id^=categoryAggregates]"));
                            self.attributeListOptions.setCategoryAggregatesCount(aggregateNames.length);
                            callback(true);
                        });
                    }).fail(function () {
                        alert(window.getRes("error_unexpected"));
                    });
                },
                onPreRemoveEventHandler: function (value, callback) {
                    var url = "api/v1/categories/aggregate/term";
                    var categoryAggregateTermDto = {
                        categoryAggregateName: value,
                        categoryCode: categoryCode,
                        categorySystemId: $("#orgClassification").attr("val")
                    };
                    $.ajax({
                        type: "DELETE",
                        url: url,
                        data: JSON.stringify(categoryAggregateTermDto),
                        contentType: 'application/json'
                    }).done(function () {
                        getAllCategoryAggregateNames().then(function (aggregateNames) {
                            var autocomplete = aggregateNames.join("~");
                            window.UpdateQuickTagAutoCompletes(autocomplete, $("input[id^=categoryAggregates]"));
                            self.attributeListOptions.setCategoryAggregatesCount(aggregateNames.length);
                            callback(true);
                        });
                    }).fail(function () {
                        alert(window.getRes("error_unexpected"));
                    });
                }
            });
        };

        // AttributeList PRIVATE Methods //

        var getAllCategoryAggregateNames = function () {
            var namesPromise = new window.Promise(function (resolve) {
                $.get("api/v1/categories/aggregate/names", function (aggregateNames) {
                    resolve(aggregateNames);
                });
            });
            return namesPromise;
        };

        var onAttributeListScrollClosure = function (batchSize, categoryAggregatesAutocomplete) {
            var _batchSize = batchSize;
            var _categoryAggregatesAutocomplete = categoryAggregatesAutocomplete;
            return function () {
                onAttributeListScroll(_batchSize, _categoryAggregatesAutocomplete);
            } 
        };

        var onAttributeListScroll = function (batchSize, categoryAggregatesAutocomplete) {
            if (!_this.haveAllBatchesBeenLoaded && !_this.isInTheProcessOfLoadingNextBatch) {
                if (_this.$attributeList[0].scrollHeight - _this.$attributeList[0].scrollTop ===
                    _this.$attributeList[0].clientHeight) {
                    _this.isInTheProcessOfLoadingNextBatch = true;
                    addTagsAbilityForNextBatch(batchSize, categoryAggregatesAutocomplete);
                }
            }
        };

        var addTagsAbilityForNextBatch = function(batchSize, categoryAggregatesAutocomplete) {
            var hiddenAttributeContainers = $('.orgClassificationAttributeContainer.hidden');
            var hiddenAttributeContainersBatch = hiddenAttributeContainers.splice(0, batchSize);
            if (hiddenAttributeContainersBatch.length === 0) {
                _this.haveAllBatchesBeenLoaded = true;
                _this.isInTheProcessOfLoadingNextBatch = false;
                _this.$attributeList.off("scroll", onAttributeListScroll);
            } else {
                _this.$loadingAnimationContainer.show();
                setTimeout(function () {
                    hiddenAttributeContainersBatch.forEach(function (elm) {
                        var $tagsContainer = $(elm).find(".orgClassificationAttributeTagsContainer");
                        var categoryCode = $tagsContainer.attr("data-code");
                        var tagsArrayJson = JSON.parse($tagsContainer.attr("data-tagArrayJson"));
                        _this.addCategoryAggregateTagsForCode(categoryCode, tagsArrayJson, categoryAggregatesAutocomplete);
                    });
                    setTimeout(function () {
                        hiddenAttributeContainersBatch.forEach(function (elm) {
                            $(elm).removeClass("hidden");
                        });
                        _this.$loadingAnimationContainer.hide();
                        _this.isInTheProcessOfLoadingNextBatch = false;
                    }, 500);
                }, 500);
            }
        };
    })(); 

    this.attributeListOptions = new (function () {
        var storePosClassificationSystemId = 0;

        // AttributeListOptions PUBLIC Method //

        this.initialize = function(storePosClassificationSystemId_in) {
            storePosClassificationSystemId = storePosClassificationSystemId_in;

            $("#categoryAggregatesCountContainer").on("click", function () {
                openCategoryAggregatesManager();
            });
        };

        this.onOrgClassificationSystemChange = function (classificationSystemId) {
            toggleAttributeListStoreSelector(classificationSystemId);
            $("#orgClassificationAttributeList").html(window.getRes("loading") + " ...");
            var storeId = getSelectedStoreId();
            reloadOrgClassificationAttributeList(classificationSystemId, storeId);
        };

        this.onAttributeListStoreSelectorChange = function(storeId) {
            var classificationSystemId = getSelectedClassificationSystemId();
            reloadOrgClassificationAttributeList(classificationSystemId, storeId); 
        };

        this.setCategoryAggregatesCount = function (count) {
            $("#categoryAggregatesCount").html(count);
        };

        // AttributeListOptions PRIVATE Method //

        var reloadOrgClassificationAttributeList = function(classificationSystemId, storeId) {
            var params = {
                classificationSystemId: classificationSystemId,
                storeId: storeId
            };
            var url = "/GetTreelineControl.aspx?controlName=/uc/preferences/categoryAggregate/OrgClassificationAttributeList.ascx&"
                + $.param(params);
            $("#orgClassificationAttributeList").html(window.getRes("loading") + " ...");
            setTimeout(function () {
                $("#orgClassificationAttributeList").load(url);
            }, 1000);
        };

        var openCategoryAggregatesManager = function () {
            var url = "/GetTreelineControl.aspx?controlName=/uc/preferences/categoryAggregate/CategoryAggregatesManager.ascx";
            window.openMultiModal({
                id: 'categoryAggregates',
                url: url,
                width: "800px",
                height: "500px",
                isFixup: true
            });
        };

        var toggleAttributeListStoreSelector = function(classificationSystemId) {
            var $attributeListStoreSelector = $("#attributeListStoreSelector");
            if (classificationSystemId === storePosClassificationSystemId) {
                $attributeListStoreSelector.removeClass("hidden"); 
            } else {
                $attributeListStoreSelector.addClass("hidden"); 
            }
        }

        var getSelectedStoreId = function() {
            return $("#availableAttributeListStores").attr("val");
        };

        var getSelectedClassificationSystemId = function () {
            return $("#orgClassification").attr("val");
        };
    })();
})();;
ePlus.modules.yourTown = (function () {
    var initialize = function (data) {
        if ($('#store-name-drop-down').length > 0) {
            $('#store-name-drop-down').on('change', function () {
                var selectedStoreId = $(this).val();

                if (!data || !data.storeAddressesJson || data.storeAddressesJson.length === 0) return;

                var storeAddresses = data.storeAddressesJson.filter(function (storeAddress) {
                    return storeAddress.id === selectedStoreId;
                });

                updateStoreInfo(storeAddresses[0]);
            });
        } 

        $('.publisher-email-form', $('#list-container')).on('submit', function (e) {
            e.preventDefault();
            submitForm(data.appUserId, data.doPromptToAddAddress, data.doPromptToContactUserAdmin);
        });
    };

    function submitForm(appUserId, doPromptToAddAddress, doPromptToContactUserAdmin) {
        var url = '/api/organizations/BDNQ/yourtown';
        var yourTownFormJson = $('.publisher-email-form', $('#list-container')).serializeJSON();

        if ($('#store-name-drop-down').length > 0) {
            yourTownFormJson.storeName = $('option:selected', $('#store-name-drop-down')).html();
        }

        var isAddressFieldEmpty = yourTownFormJson.streetAddress1 === "" && yourTownFormJson.postalCode === "";

        if (isAddressFieldEmpty && doPromptToAddAddress) {
            if (confirm(getRes('would_you_like_to_add_a_default_organization_address'))) {
                var onClose = function () {
                    var storeAddress = {
                        streetAddress1: $('#address1').val(),
                        streetAddress2: $('#address2').val(),
                        postalCode: $('#zip').val()
                    };

                    updateStoreInfo(storeAddress);
                    closeModal();
                };
                openAccountInfoEdit(appUserId, onClose);

                return;
            }
        } else if (isAddressFieldEmpty && doPromptToContactUserAdmin) {
            alert(getRes('you_can_enter_an_address_contact_user_admin_a_default_organization_address'));
        }

        $('#town-send-email-submit').hide();

        $.ajax({
            type: 'POST',
            url: url,
            data: yourTownFormJson
        })
            .done(function () {
                reloadPage();
            })
            .fail(function (err) {
                var responseText = JSON.parse(err.responseText);
                alert(responseText.message);
                $('#town-send-email-submit').show();
            });
    }

    function updateStoreInfo(storeAddress) {
        $('#street-address-1').val(storeAddress.streetAddress1);
        $('#street-address-2').val(storeAddress.streetAddress2);
        $('#postal-code').val(storeAddress.postalCode);
    }

    return {
        initialize: initialize
    }
})();;
window.ePlus.modules.listViewAffiliation = (function () {
    var MEMBER_ROLE_TYPE = 3;
    var listView = ePlus.modules.listView;

    var initializeAffiliationOtherActions = function () {
        if (getListViewProperty('resultType') === getEnumValue('resultType', 'ELIGIBLEAFFILIATIONS')) {
            $('#join-selected-affiliations').off().on('click', function () {
                $('.otherActions').webuiPopover('hide');
                window.ePlus.modules.listViewAffiliation.joinSelectedAffiliations();
            });

            $('#hide-selected-affiliations').off().on('click', function () {
                $('.otherActions').webuiPopover('hide');
                ePlus.modules.listViewAffiliation.hideSelectedAffiliations(doShowHidden());
            });

            $('#unhide-selected-affiliations').off().on('click', function () {
                $('.otherActions').webuiPopover('hide');
                ePlus.modules.listViewAffiliation.unhideSelectedAffiliations(doShowHidden());
            });
        }

        if (getListViewProperty('resultType') === getEnumValue('resultType', 'JOINEDAFFILIATIONS')) {
            $('#leave-selected-affiliations').off().on('click', function () {
                $('.otherActions').webuiPopover('hide');
                window.ePlus.modules.listViewAffiliation.leaveSelectedAffiliations();
            });
        }
    };

    var joinAllAccessAffiliation = function (affiliationId, appUserId, type) {
        var JOIN_AFFILIATION = 1;
        toggleAllAccessAffiliationStatus(affiliationId, appUserId, type, JOIN_AFFILIATION, function () {
            incrementJoinedAffiliationCount();
            window.removeListViewItemAndUpdateListView(affiliationId);
        });
    };

    var joinOrgLevelAffiliation = function (affiliationId, orgId, orgType) {
        var url = getAffiliationApiUrl(affiliationId) + '/organizations/' + orgId;
        var groupInfo = {
            orgId: orgId,
            orgType: orgType
        };

        createGroupRecord(url, groupInfo, affiliationId);
    };   

    var joinSelectedAffiliations = function () {
        var affiliationIds = window.getSelectedItems();

        affiliationIds.forEach(function (id) {
            var affiliationProps = getAffiliationDataProperties($('#affiliation-action-' + id));
            if (affiliationProps.isUserLevel) {
                joinUserLevelAffiliation(affiliationProps.affiliationId, affiliationProps.appUserId, MEMBER_ROLE_TYPE);
            } else {
                var confirmationMessage = window.getRes('if_you_join_every_user_in_your_organization_will_join_this_group_name ') + $('.accFont', $('#as_' + id)).html();
                if (confirm(window.getRes(confirmationMessage))) {
                    joinOrgLevelAffiliation(id, affiliationProps.orgId, affiliationProps.orgType);
                }
            }
        });
    };

    var joinUserLevelAffiliation = function (affiliationId, appUserId, roleType) {
        var url = getAffiliationApiUrl(affiliationId) + '/users/' + appUserId + '?roleType=' + roleType;
        createGroupRecord(url, null, affiliationId);
    };

    var joinLegacyVerifiedAffiliation = function (affiliationId, appUserId, type) {
        var affCode = prompt(getRes("affiliation_validation_message"));
        if (affCode != null && affCode != "") {
            $.getJSON("/GetJSONData.aspx?m=User&builder=validateAffiliation", {
                groupID: affiliationId,
                affCode: affCode,
                validType: type,
                appUserID: appUserId
            },
                function (data) {
                    if (data.code == "SUCCESS") {
                        alert(getRes("success"));
                        incrementJoinedAffiliationCount();
                        window.removeListViewItemAndUpdateListView(affiliationId);
                    }
                    else {
                        alert(data.text);
                    }
                });
        }
    };

    var hideSelectedAffiliations = function (doShowHidden) {
        var affiliationIds = window.getSelectedItems();

        var resultType = window.getListViewProperty('resultType');
        affiliationIds.forEach(function (id) {
            window.hideItem(id, null, resultType, doShowHidden, null);

            if (!doShowHidden && !_.isNil($("#hiddenItems").html())) {
                window.updateHiddenCount(id.itemId, false);
                window.removeListViewItem(id.itemId);
                window.updateRefineFilterCounts();
            }

            window.hideSimpleAction(resultType + '_' + id.itemId);
        });

        if (_.isFunction(window.initializeAutoComplete)) {
            window.initializeAutoComplete();
        }
    };

    var leaveAllAccessAffiliation = function (affiliationId, appUserId, type) {
        var LEAVE_AFFILIATION = 0;
        toggleAllAccessAffiliationStatus(affiliationId, appUserId, type, LEAVE_AFFILIATION, function () {
            incrementEligibleAffiliationCount();
            window.removeListViewItemAndUpdateListView(affiliationId);
        });
    };

    var leaveOrgLevelAffiliation = function (affiliationId, orgId, orgType) {
        var url = getAffiliationApiUrl(affiliationId) + '/organizations/' + orgId;
        var orgInfo = {
            orgId: orgId,
            orgType: orgType
        };

        deleteGroupRecord(url, orgInfo, affiliationId);
    };

    var leaveSelectedAffiliations = function () {
        var affiliationIds = window.getSelectedItems();

        affiliationIds.forEach(function (id) {
            var affiliationProps = getAffiliationDataProperties($('#affiliation-action-' + id));
            if (affiliationProps.isUserLevel) {
                leaveUserLevelAffiliation(affiliationProps.affiliationId, affiliationProps.appUserId, MEMBER_ROLE_TYPE);
            } else {
                var confirmationMessage = window.getRes('if_you_leave_every_user_in_your_organization_will_leave_from_group_name ') + $('.accFont', $('#as_' + id)).html();
                if (confirm(confirmationMessage)) {
                    leaveOrgLevelAffiliation(id, affiliationProps.orgId, affiliationProps.orgType);
                }
            }
        });
    }

    var leaveUserLevelAffiliation = function (affiliationId, appUserId, roleType) {
        var url = getAffiliationApiUrl(affiliationId) + '/users/' + appUserId + '?roleType=' + roleType;

        deleteGroupRecord(url, null, affiliationId);
    };

    var unhideSelectedAffiliations = function (doShowHidden) {
        var affiliationIds = window.getSelectedItems();

        var resultType = window.getListViewProperty('resultType');
        affiliationIds.forEach(function (id) {
            window.unhideItem(id, null, resultType, doShowHidden, null);

            if (!doShowHidden && !_.isNil($("#hiddenItems").html())) {
                window.updateHiddenCount(id.itemId, true);
                window.removeListViewItem(id.itemId);
                window.updateRefineFilterCounts();
            }

            window.hideSimpleAction(resultType + '_' + id.itemId);
        });

        if (_.isFunction(window.initializeAutoComplete)) {
            window.initializeAutoComplete();
        }
    };

    var createGroupRecord = function (url, data, affiliationId) {
        $.ajax({
            type: 'POST',
            url: url,
            data: data
        })
            .done(function (result) {
                incrementJoinedAffiliationCount();
                listView.removeListViewItemAndUpdateListView(affiliationId);

                alert(result);
            })
            .fail(function (err) {
                var response;
                if (err && err.responseText) {
                    response = JSON.parse(err.responseText);
                }

                if (response && response.message) {
                    alert(response.message);
                } else {
                    alert(window.getRes('unexpected_error'));
                }
            });
    }

    var deleteGroupRecord = function (url, data, affiliationId) {
        $.ajax({
            type: 'DELETE',
            url: url,
            data: data
        })
            .done(function (result) {
                incrementEligibleAffiliationCount();
                listView.removeListViewItemAndUpdateListView(affiliationId);

                alert(result);
            })
            .fail(function (err) {
                var responseText = JSON.parse(err.responseText);
                alert(responseText && responseText.message ? responseText.message : window.getRes('error_unexpected'));
            });
    };

    var getAffiliationDataProperties = function ($element) {
        return $element.data('affiliation-properties');
    };
    
    var getAffiliationApiUrl = function (affiliationId) {
        return '/api/affiliations/' + affiliationId;
    }

    var toggleAllAccessAffiliationStatus = function (affiliationId, appUserId, type, add, callback) {
        $.getJSON("/GetJSONData.aspx?m=User&builder=allAccessAffiliation", {
            groupID: affiliationId,
            validType: type,
            add: add,
            appUserID: appUserId
        },
            function (data) {
                if (data.code == "SUCCESS") {
                    if (typeof callback === 'function') {
                        callback();
                    }
                }
            });
    }

    var incrementEligibleAffiliationCount = function () {
        var $eligibleToJoinCount = $('#value_' + getEnumValue('resultType', 'ELIGIBLEAFFILIATIONS'));
        if ($eligibleToJoinCount.length > 0) {
            var count = parseInt($eligibleToJoinCount.html(), 10);
            if (!isNaN(count)) {
                $eligibleToJoinCount.html(count + 1);
            }
        }
    }

    var decrementEligibleAffiliationCount = function () {
        var $eligibleToJoinCount = $('#value_' + getEnumValue('resultType', 'ELIGIBLEAFFILIATIONS'));
        if ($eligibleToJoinCount.length > 0) {
            var count = parseInt($eligibleToJoinCount.html(), 10);
            if (!isNaN(count) && count > 0) {
                $eligibleToJoinCount.html(count - 1);
            }
        }
    }

    var incrementJoinedAffiliationCount = function () {
        var $joinedAffiliationCount = $('#value_' + getEnumValue('resultType', 'JOINEDAFFILIATIONS'));
        if ($joinedAffiliationCount.length > 0) {
            var count = parseInt($joinedAffiliationCount.html(), 10);
            if (!isNaN(count)) {
                $joinedAffiliationCount.html(count + 1);
            }
        }
    }

    return {
        decrementEligibleAffiliationCount: decrementEligibleAffiliationCount,
        getAffiliationDataProperties: getAffiliationDataProperties,
        initializeAffiliationOtherActions: initializeAffiliationOtherActions,
        joinAllAccessAffiliation: joinAllAccessAffiliation,
        joinOrgLevelAffiliation: joinOrgLevelAffiliation,
        joinSelectedAffiliations: joinSelectedAffiliations,
        joinUserLevelAffiliation: joinUserLevelAffiliation,
        joinLegacyVerifiedAffiliation: joinLegacyVerifiedAffiliation,
        hideSelectedAffiliations: hideSelectedAffiliations,
        leaveAllAccessAffiliation: leaveAllAccessAffiliation,
        leaveOrgLevelAffiliation: leaveOrgLevelAffiliation,
        leaveSelectedAffiliations: leaveSelectedAffiliations,
        leaveUserLevelAffiliation: leaveUserLevelAffiliation,
        unhideSelectedAffiliations: unhideSelectedAffiliations,
        MEMBER_ROLE_TYPE: MEMBER_ROLE_TYPE
    }
})();;
; window.ePlus.modules.prc = (function () {
    var res = window.ePlus.resources;

    var grantUserProfileAccess = function (appUserId, groupId, callback) {
        if (appUserId && groupId) {
            $.ajax({
                type: "POST",
                url: "api/users/" + appUserId + "/groups/" + groupId,
                contentType: "application/json",
                complete: function () {
                    if (typeof callback === "function") {
                        callback();
                    }
                },
                error: function(request, status, error) {
                    alert(request.responseText);
                }
            });
        } else {
            console.warn("Cannot grant profile access. Invalid appUserId and/or profileId.");
        }
    }

    var removeUserProfileAccess = function (appUserId, groupId, callback) {
        if (appUserId && groupId) {
            $.ajax({
                type: "DELETE",
                url: "api/users/" + appUserId + "/groups/" + groupId,
                contentType: "application/json",
                complete: function () {
                    if (typeof callback === "function") {
                        callback();
                    }
                },
                error: function(request, status, error) {
                    alert(request.responseText);
                }
            });
        } else {
            console.warn("Cannot remove profile access. Invalid appUserId and/or profileId.");
        }
    }

    var toggleUserProfileAccess = function (doGrantAccess, appUserId, groupId, callback) {
        doGrantAccess ? grantUserProfileAccess(appUserId, groupId, callback) : removeUserProfileAccess(appUserId, groupId, callback);
    }

    var savePrcRequest = function (requestType, lineItemRequestType, lineItem, callback) {
        $.ajax({
            type: requestType,
            url: "api/products/requests/" + lineItemRequestType,
            contentType: "application/json",
            dataType: "json",
            data: JSON.stringify(lineItem),
            success: function (data) {
                if (typeof callback === "function") {
                    callback();
                }
            },
            error: function (request, status, error) {
                var hasInvalidAddresses = request && !request.responseText;
                if (!hasInvalidAddresses) {
                    notifyUserOfInvalidShippingAddress(request.responseText);
                } else {
                    alert(request.responseText);
                }
            }
        });
    }

    function notifyUserOfInvalidShippingAddress(responseText) {
        var responseTextJson = JSON.parse(responseText)
        var invalidAddressResults = JSON.parse(responseTextJson.message);
        var errorMessage = "";

        if (invalidAddressResults && invalidAddressResults.length > 0 && invalidAddressResults[0]) {
            invalidAddressResults = invalidAddressResults[0];

            invalidAddressResults.forEach(function (invalidAddressResult) {
                errorMessage += invalidAddressResult.ErrorMessage + "\n";
            });
        } else {
            errorMessage = res.getRes("error_unexpected");
        }

        alert(errorMessage);
    }

    var undoPrcRequest = function (lineItemRequestType, lineItem, callback) {
        lineItem.UserRequestOrderId = null;
        lineItem.ApprovedQty = null;
        lineItem.Status = 0;

        savePrcRequest("PUT", lineItemRequestType, lineItem, callback);
    }

    var deletePrcRequest = function (lineItemRequestType, userRequestOrderLineItemId, callback) {
        $.ajax({
            type: "DELETE",
            url: "api/products/requests/" + lineItemRequestType + "/" + userRequestOrderLineItemId,
            contentType: "application/json",
            dataType: "json",
            complete: function () {
                if (typeof callback === "function") {
                    callback();
                }
            },
            error: function (request, status, error) {
                alert(request.responseText);
            }
        });
    }

    var saveUserRequestOrder = function (type, userRequestOrder, onSuccess, onError) {
        $.ajax({
            type: type,
            url: "api/products/requests/orders",
            data: JSON.stringify(userRequestOrder),
            cache: false,
            contentType: "application/json",
            success: onSuccess,
            error: onError
        });
    }

    var getDistributorOptions = function (sku, profileId) {
        return $.ajax({
            url: '/api/products/' + sku + '/profiles/' + profileId + '/distributors',
            type: 'GET',
            contentType: 'application/json'
        });
    };

    var getDistributorShippingOptions = function (profileId, distributorId, address) {
        return $.ajax({
            url: '/api/products/requests/profiles/' + profileId + '/distributors/' + distributorId + '/shippingOptions',
            type: 'GET',
            contentType: 'application/json',
            data: address
        });
    };

    var getPrcTitleApiUri = function (method, prcTitle) {
        var uri = '/api/reviewcopies/print/' + prcTitle.orgId
                + '/profiles/' + prcTitle.groupId
                + '/titles';

        if (method === 'PUT') {
            uri += '/' + prcTitle.sku;
        }
        return uri;
    }

    var sendPrcTitleAjaxRequest = function (method, prcTitle) {
        var options = {
            type: method,
            url: getPrcTitleApiUri(method, prcTitle)
        }

        if (prcTitle) {
            options.contentType = 'application/json';
            options.data = JSON.stringify(prcTitle);
        }

        return $.ajax(options);
    };

    var createPrcTitle = function (prcTitle) {
        return sendPrcTitleAjaxRequest('POST', prcTitle);
    };

    var updatePrcTitle = function (prcTitle) {
        return sendPrcTitleAjaxRequest('PUT', prcTitle);
    };

    var getDistributorProductsApiUri = function (prcTitle) {
        return '/api/reviewcopies/print/' + prcTitle.orgId
             + '/profiles/' + prcTitle.groupId
             + '/titles/' + prcTitle.sku
             + '/distributorProducts';
    };

    var sendDistributorProductsAjaxRequest = function (method, prcTitle, distributorProducts) {
        var options = {
            type: method,
            url: getDistributorProductsApiUri(prcTitle)
        };

        if (distributorProducts) {
            options.contentType = 'application/json';
            options.data = JSON.stringify(distributorProducts);
        }

        return $.ajax(options);
    }

    var createDistributorProducts = function (prcTitle, distributorProducts) {
        return sendDistributorProductsAjaxRequest('POST', prcTitle, distributorProducts);
    };

    var deleteDistributorProducts = function (prcTitle) {
        return sendDistributorProductsAjaxRequest('DELETE', prcTitle);
    };

    var updateUserRequestOrderLineItemShipDate = function (userRequestOrderLineItemId, date, callback) {
        return $.ajax({
            type: 'PATCH',
            url: '/api/products/requests/shipping/' + userRequestOrderLineItemId,
            contentType: 'application/json',
            data: JSON.stringify(date),
            success: function () {
                if (typeof callback === 'function') {
                    callback();
                }
            }
        });
    };

    return {
        savePrcRequest: savePrcRequest,
        deletePrcRequest: deletePrcRequest,
        saveUserRequestOrder: saveUserRequestOrder,
        undoPrcRequest: undoPrcRequest,
        grantUserProfileAccess: grantUserProfileAccess,
        removeUserProfileAccess: removeUserProfileAccess,
        toggleUserProfileAccess: toggleUserProfileAccess,
        getDistributorOptions: getDistributorOptions,
        getDistributorShippingOptions: getDistributorShippingOptions,
        createPrcTitle: createPrcTitle,
        updatePrcTitle: updatePrcTitle,
        createDistributorProducts: createDistributorProducts,
        deleteDistributorProducts: deleteDistributorProducts,
        updateUserRequestOrderLineItemShipDate: updateUserRequestOrderLineItemShipDate
    }
})();;
;
window.ePlus.modules.prc.ui = window.ePlus.modules.prc.ui || {};
window.ePlus.modules.prc.ui.modals = window.ePlus.modules.prc.ui.modals || {};
window.ePlus.modules.prc.ui.modals.manageTitle = (function () {
    var api = window.ePlus.modules.prc;
    var http = window.ePlus.http;
    var defaults = {
        sku: null,
        modalId: 'manage-prc-title-modal'
    };
    var settings;

    var getModalView = function () {
        return settings.sku ? '/prcs/' + settings.sku + '/manage' : '/prcs/manage';
    };

    var open = function (options) {
        settings = $.extend({}, defaults, options);

        if (settings.sku) {
            settings.modalId += '-' + settings.sku;
        }

        openMultiModal({
            id: settings.modalId,
            url: getModalView(),
            width: '500px',
            height: '300px',
            onLoad: function (elem) {
                initialize(elem);
            }
        });
    };

    var toggleCheckboxAndGetChecked = function ($checkbox) {
        return $checkbox.toggleClass('box_checked box_unchecked').hasClass('box_checked');
    };

    var initializeFormSubmission = function ($form) {
        $form.on('submit', function () {
            onFormSubmission(this);
            return false;
        });
    };

    var initializeDistributorCheckboxes = function ($form) {
        $('div.distributor', $form).on('click', function () {
            var $this = $(this);
            var checked = toggleCheckboxAndGetChecked($this);
            var $input = $('input', $this);
            
            $input.val(checked ? $input.data('distributorid') : '').prop('disabled', !checked);
            $('input.distributor-sku', $this.closest('tr')).prop('disabled', !checked);
        });
    };

    var initializeMakeActiveCheckbox = function ($form) {
        $('div.status', $form).on('click', function () {
            var $this = $(this);
            var checked = toggleCheckboxAndGetChecked($this);

            $('input', $this).val(checked
                ? getEnumValue('prcTitleStatus', 'ACTIVE')
                : getEnumValue('prcTitleStatus', 'INACTIVE')
            );
        });
    };

    var initialize = function (elem) {
        var $form = $('form', elem);

        initializeFormSubmission($form);
        initializeDistributorCheckboxes($form);
        initializeMakeActiveCheckbox($form);
    };
    
    var getFormData = function (form) {
        var $form = $(form);

        $('input[name="prcTitle[sku]"]', $form).prop('disabled', false);

        var formData = $form.serializeJSON();

        return {
            prcTitleAction: getPrcTitleAction(formData.isNew),
            prcTitle: formData.prcTitle,
            distributorProducts: getDistributorProductsFormData(formData.distributorProducts)
        };
    };

    var getPrcTitleAction = function (isNew) {
        return isNew ? api.createPrcTitle : api.updatePrcTitle;
    };

    var getDistributorProductsFormData = function (distributorProducts) {
        return _(distributorProducts).filter('distributorId').value();
    };

    var submitForm = function (formData, onSuccess) {
        formData.prcTitleAction(formData.prcTitle)
            .done(function () {
                api.deleteDistributorProducts(formData.prcTitle)
                    .done(function () {
                        api.createDistributorProducts(formData.prcTitle, formData.distributorProducts)
                            .done(onSuccess)
                            .fail(onDistributorProductsActionFailure);
                    })
                    .fail(onDistributorProductsActionFailure);
            })
            .fail(onPrcActionFailure);
    };

    var onFormSubmission = function (form) {
        var formData = getFormData(form);
        
        submitForm(formData, function () {
            setListViewProperty('reload', true);
            closeMultiModal(settings.modalId);
            refreshPromoteModal(settings.sku);
        });
    };

    var getPrcActionFailureMessage = function (statusCode) {
        switch (statusCode) {
            case http.statusCode.CONFLICT:
                return getRes('error_prc_already_exists');
            default:
                return http.getHttpStatusCodeErrorMessage(statusCode);
        }
    };

    var onPrcActionFailure = function (jqXHR) {
        showFailureMessageAlert(jqXHR.status, getPrcActionFailureMessage);
    };

    var onDistributorProductsActionFailure = function (jqXHR) {
        showFailureMessageAlert(jqXHR.status, http.getHttpStatusCodeErrorMessage);
    };

    var showFailureMessageAlert = function (statusCode, failureMessageHandler) {
        var message = failureMessageHandler(statusCode);

        if (!message) return;

        alert(message);
    };

    return {
        open: open
    };
})();
;
;
window.ePlus.modules.listView.prc = (function () {
    var modals = ePlus.modules.prc.ui.modals;
    var res = window.ePlus.resources;
    var util = window.ePlus.util;
    var listView = window.ePlus.modules.listView;

    var getSelectedPrcProfileId = function () {
        return listView.getListViewProperty('selectedPrcProfileId');
    };

    var setSelectedPrcProfileId = function (profileId) {
        listView.setListViewProperty('selectedPrcProfileId', profileId);
    };
    
    var initialize = function () {
        var resultType = listView.getListViewProperty('resultType');

        if (resultType === util.getEnumValue('resultType', 'PRCSADMIN')) {
            initializePrcAdminListView();
        } else {
            initializePrcRequestListView();
        }
    };

    var initializePrcAdminListView = function () {
        initializeAddPrcTitleButton();
        initializeDistributorPopOver();
        initializePrcTitleStatusCheckboxes();
    };

    var initializePrcRequestListView = function () {
        initializeProfileContentExpander();
        initializeOriginalQuantityPreviews();
        initializeRequestMessageExpander();
        initializeDistributorSelector();
        initializeShippingAddressPopOver();
        initializeDatePickers();
    };

    var initializeAddPrcTitleButton = function () {
        $('.add-prc-title', '#top-menu').on('click', function () {
            modals.manageTitle.open();
        });
    };
 
    var initializeDistributorPopOver = function () {
        $('.prc-distributors', '#itemContainer').webuiPopover({
            type: 'async',
            trigger: 'hover',
            placement: 'left',
            cache: true
        });
    };

    var initializePrcTitleStatusCheckboxes = function () {
        $('.prc-title-status', '#itemContainer').on('click', function () {
            updatePrcTitleStatus(this);
        });
    };

    var initializeProfileContentExpander = function () {
        var moreText = res.getRes("more");
        var lessText = res.getRes("less");
        var charLimit = 150;

        $(document)
            .off('click', '.more-link')
            .on('click', '.more-link', function () {
                var $this = $(this);
                if ($this.hasClass('less')) {
                    $this.removeClass('less');
                    $this.html(moreText);
                } else {
                    $this.addClass('less');
                    $this.html(lessText);
                }
                $this.parent().prev().toggle();
                $this.prev().toggle();
                return false;
            });

        $(".more").each(function () {
            var $this = $(this);
            $this.removeClass("dotDot");
            if ($this.hasClass("shortened")) return;

            $this.addClass("shortened");
            var content = $this.html();
            if (content.length > charLimit) {
                var c = content.substr(0, charLimit);
                var h = content.substr(charLimit, content.length - charLimit);
                var html = c + '<span class="more-ellipses"></span> <span class="more-content"><span>' + h + '</span> <a href="#" class="more-link ePlusLink clickable">' + moreText + '</a></span>';
                $this.html(html);
                $(".more-content span").hide();
            }
        });
    };

    var initializeOriginalQuantityPreviews = function () {
        for (var i = 0; window.items && i < window.items.length; i++) {
            var $input = $("#approvedQty" + window.items[i]);
            var currentValue = $input.val();
            var requestValue = $input.attr("data-requested-quantity");
            if (currentValue !== requestValue) {
                $("#qty-message-" + window.items[i]).show().webuiPopover({
                    trigger: "click",
                    cache: false,
                    content: "<span class='bold'>" + res.getRes("requested_quantity") + ": </span>" + requestValue,
                    placement: 'bottom-right',
                    container: "#pageContent"
                });
            }
        }
    };

    var initializeRequestMessageExpander = function () {
        $('.req-purpose-icon', $('#itemContainer')).click(function () {
            var $icon = $(this);
            $icon.toggleClass("icon-drop-up-icon-01 icon-drop-down-icon");
            var requestId = $icon.data('request-id');
            $('#req-message-' + requestId).toggleClass('hidden');
            $('#req-message-full-' + requestId).toggleClass('hidden');
        });
    };

    var initializeDistributorSelector = function () {
        initializeDistributorIdSelector();
        initializeDistributorShippingCodeSelector();

        $('select.distributor-id', '#itemContainer').trigger('change');
    };

    var initializeShippingAddressPopOver = function () {
        $('.ship-to', '#itemContainer').webuiPopover({
            type: 'async',
            trigger: 'hover',
            delay: 300
        });
    };

    var initializeDatePickers = function () {
        // Within datepicker, the en-US culture is stored as empty string, not en-US.
        var userCulture = window.ePlus.user.culture === 'en-US' ? '' : window.ePlus.user.culture;
        var culture = $.datepicker.regional[userCulture];
        var options = $.extend(
            {},
            culture,
            {
                onSelect: function () {
                    var $this = $(this);
                    var $parent = $this.parent();
                    var userRequestOrderLineItemId = $this.data('user-request-order-line-item-id');
                    var date = $this.val();
                    var callback = function () {
                        // Once the ship date has been updated, hide the datepicker and just show the date in plain text.
                        $this.hide();
                        $parent.append(date);
                    };

                    // Disable the datepicker to indicate the page is loading.
                    $this.prop('disabled', true);

                    ePlus.modules.prc.updateUserRequestOrderLineItemShipDate(userRequestOrderLineItemId, date, callback);
                }
            }
        );

        $('.set-ship-date', '#itemContainer').each(function () {
            $(this).datepicker(options);
        });
    };

    var initializeDistributorIdSelector = function () {
        $('select.distributor-id', '#itemContainer')
            .on('change', function () {
                var $prcRow = getParentPrcRow(this);
                var userRequestOrderLineItem = getPrcRowUserRequestOrderLineItem($prcRow);

                userRequestOrderLineItem.DistributorId = parseInt($(this).val());
                userRequestOrderLineItem.ShippingCode = null;

                setPrcRowUserRequestOrderLineItem($prcRow, userRequestOrderLineItem);
                updateDistributorShippingOptions($prcRow, userRequestOrderLineItem, function () {
                    $('select.distributor-shipping-code', $prcRow).trigger('change');
                });
            });
    };

    var initializeDistributorShippingCodeSelector = function () {
        $('select.distributor-shipping-code', '#itemContainer')
            .on('change', function () {
                var $prcRow = getParentPrcRow(this);
                var userRequestOrderLineItem = getPrcRowUserRequestOrderLineItem($prcRow);

                userRequestOrderLineItem.ShippingCode = $(this).val();
                userRequestOrderLineItem.Shipping.MethodDescription = $(this).children('option:selected').html();

                setPrcRowUserRequestOrderLineItem($prcRow, userRequestOrderLineItem);
            });
    };

    var updateDistributorShippingOptions = function ($prcRow, userRequestOrderLineItem, callback) {
        var profileId = getSelectedPrcProfileId();
        var $select = $prcRow.find('select.distributor-shipping-code');

        removeDistributorShippingOptions($select)

        ePlus.modules.prc.getDistributorShippingOptions(profileId, userRequestOrderLineItem.DistributorId, userRequestOrderLineItem.Address)
            .done(function (shippingOptions) {
                addDistributorShippingOptions($select, shippingOptions);
            })
            .fail(function () {
                alert(res.getRes('error_unexpected'));
            })
            .always(callback);
    };

    var removeDistributorShippingOptions = function ($select) {
        $select.children().remove();
    };

    var addDistributorShippingOptions = function ($select, shippingOptions) {
        if (shippingOptions && shippingOptions.length > 0) {
            for (var i = 0, max = shippingOptions.length; i < max; i++) {
                var shippingOption = shippingOptions[i];
                var $option = $('<option />', {
                    text: shippingOption.shippingDescription,
                    value: shippingOption.shippingCode
                });

                $select.append($option);
            }
        }
    };

    var getPrcRowUserRequestOrderLineItem = function ($prcRow) {
        return $prcRow.data('user-request-order-line-item');
    };

    var setPrcRowUserRequestOrderLineItem = function ($prcRow, userRequestOrderLineItem) {
        $prcRow.data('user-request-order-line-item', userRequestOrderLineItem);
    };

    var getParentPrcRow = function (elem) {
        return $(elem).closest('.prc-row');
    };

    var updatePrcTitleStatus = function (elem) {
        var $row = $(elem).closest('tr');
        var prcTitle = $row.data('prc-title');
        var activeDistributors = $row.data('active-distributors');
        
        prcTitle.status = $(elem).is(':checked') ? 1 : 0;

        if (activeDistributors > 0) {
            var message = getMessage(prcTitle.status);

            if (!confirm(message)) {
                $(elem).prop('checked', !prcTitle.status);
                return;
            }
        }

        ePlus.modules.prc.updatePrcTitle(prcTitle)
            .done(function () {
                reloadCurrentPage();
            })
            .fail(function () {
                alert(res.getRes('error_unexpected'));
            });
    };

    var getMessage = function (prcTitleStatus) {
        if (prcTitleStatus === 1) {
            return res.getRes('are_you_sure_you_want_to_make_title_printable');
        } else {
            return res.getRes('are_you_sure_you_want_to_remove_title_printable');
        }
    };
        
    return {
        getSelectedPrcProfileId: getSelectedPrcProfileId,
        setSelectedPrcProfileId: setSelectedPrcProfileId,
        initialize: initialize
    };
})();;
; window.ePlus.modules.listViewPrcRow = (function () {
    var res = window.ePlus.resources;
    var util = window.ePlus.util;
    var listView = window.ePlus.modules.listView;

    var removeLineItemFromUserRequestOrder = function (lineItemRequestType, userRequestOrderLineItemId) {
        var lineItem = getLineItem(userRequestOrderLineItemId);
        window.ePlus.modules.prc.undoPrcRequest(lineItemRequestType, lineItem, reloadCurrentPage);
    }

    var saveUserRequestOrderLineItem = function (lineItemRequestType, userRequestOrderLineItemId, callback) {
        var lineItem = getLineItem(userRequestOrderLineItemId);
        var newApprovedQty = document.getElementById("approvedQty" + userRequestOrderLineItemId).value;

        if (lineItem.RequestedQty < newApprovedQty && !confirm(res.getRes("you_approved_more_than_requested_are_you_sure"))) {
            return;
        }

        lineItem.ApprovedQty = newApprovedQty;

        window.ePlus.modules.prc.savePrcRequest("PUT", lineItemRequestType, lineItem, callback);
    }

    function getLineItem(userRequestOrderLineItemId) {
        var lineItem = $('#as_' + userRequestOrderLineItemId).data('user-request-order-line-item');

        if (!lineItem.ApprovedQty) {
            lineItem.ApprovedQty = lineItem.RequestedQty;
        }

        return lineItem;
    }

    var buildLineItemsFromListView = function (listViewLineItems, lineItemStatus, approvedQty) {
        lineItems = [];

        _.forEach(listViewLineItems, function (item, i) {
            var lineItem = item;
            lineItem.Status = lineItemStatus;
            lineItem.ApprovedQty = approvedQty;
            lineItems.push(lineItem);
        });
    }

    var saveNewLineItemQuantityValue = function (lineItemRequestType, userRequestOrderLineItemId) {
        var saveInput = document.getElementById("approvedQty" + userRequestOrderLineItemId);
        var oldQuantity = saveInput.dataset.originalQuantity;
        var newApprovedQty = saveInput.value;
        if (oldQuantity !== newApprovedQty) {
            var requestQty = saveInput.dataset.requestedQuantity;
            saveInput.classList.toggle("unSavedUnits");
            saveUserRequestOrderLineItem(lineItemRequestType, userRequestOrderLineItemId, function() {
                saveInput.classList.toggle("unSavedUnits");
                var qtyMessage = document.getElementById("qty-message-" + userRequestOrderLineItemId);
                if (requestQty !== newApprovedQty) {
                    qtyMessage.style.display = "initial";
                    $("#qty-message-" + userRequestOrderLineItemId).webuiPopover({
                        trigger: "click",
                        cache: false,
                        content: "<span class='bold'>" + res.getRes("requested_quantity") + ": </span>" + requestQty,
                        placement: 'bottom',
                        container: "#pageContent"
                    });
                } else {
                    qtyMessage.style.display = "none";
                }
            });
        }
    }

    var setLineItemStatus = function (lineItemRequestType, userRequestOrderLineItemId, status) {
        var lineItem = getLineItem(userRequestOrderLineItemId);

        lineItem.status = status;

        ePlus.modules.prc.savePrcRequest("PUT", lineItemRequestType, lineItem, function () {
            removeListViewItemsAndUpdateListView([userRequestOrderLineItemId]);
            $("#as_" + userRequestOrderLineItemId).slideUp();

            updateDashboardValue();
        });
    }

    var checkLineItemKey = function (elem, event) {
        if (!event) return false;
        var $e = $(elem);

        switch (event.which) {
        case 13: // ENTER
            $e.blur();
            return false;
        }
        return false;
    }

    var enableUserRequestOrderApprovedQuantity = function (userRequestOrderLineItemId) {
        var approvedQuantityElement = document.getElementById("approvedQty" + userRequestOrderLineItemId)
        var editElement = document.getElementById("editUserRequestOrder" + userRequestOrderLineItemId);
        var saveElement = document.getElementById("saveUserRequestOrder" + userRequestOrderLineItemId);
        var cancelElement = document.getElementById("cancelEditUserRequestOrder" + userRequestOrderLineItemId);
        var undoElement = document.getElementById("undoUserRequestOrder" + userRequestOrderLineItemId);

        approvedQuantityElement.disabled = false;
        editElement.classList.add("hidden");
        saveElement.classList.remove("hidden");
        cancelElement.classList.remove("hidden");
        undoElement.classList.add("hidden");
    }

    var disableUserRequestOrderApprovedQuantity = function (userRequestOrderLineItemId, doResetApprovedQty) {
        var approvedQuantityElement = document.getElementById("approvedQty" + userRequestOrderLineItemId)
        var editElement = document.getElementById("editUserRequestOrder" + userRequestOrderLineItemId);
        var saveElement = document.getElementById("saveUserRequestOrder" + userRequestOrderLineItemId);
        var cancelElement = document.getElementById("cancelEditUserRequestOrder" + userRequestOrderLineItemId);
        var undoElement = document.getElementById("undoUserRequestOrder" + userRequestOrderLineItemId);

        approvedQuantityElement.disabled = true;
        editElement.classList.remove("hidden");
        saveElement.classList.add("hidden");
        cancelElement.classList.add("hidden");
        undoElement.classList.remove("hidden");

        if (doResetApprovedQty) {
            approvedQuantityElement.value = approvedQuantityElement.getAttribute("data-original-quantity");
        }
    }

    var sendUserRequestOrderLineItem = function (orderType, status, lineItemStatus, lineItem, distributor, orgId, userId, callback) {
        var today = new Date();

        var userRequestOrder = {
            userRequestOrderId: 0,
            name: today.toISOString(),
            type: orderType,
            status: status,
            distributor: distributor,
            auditOrgId: orgId,
            auditUserId: userId
        };

        var onSuccess = function (newUserRequestOrder) {
            lineItem.UserRequestOrderId = newUserRequestOrder.userRequestOrderId;
            lineItem.status = lineItemStatus;            

            window.ePlus.modules.prc.savePrcRequest("PATCH", null, [lineItem],
                function () {
                    if (listView.getListViewProperty("itemType") === util.getEnumValue("itemType", "USERREQUESTORDERLINEITEM")) {
                        var userRequestOrderLineItemId = lineItem.UserRequestOrderLineItemId;
                        removeListViewItemsAndUpdateListView([userRequestOrderLineItemId]);
                        $("#as_" + userRequestOrderLineItemId).slideUp();

                        updateDashboardValue();
                    }
                    
                    if (typeof callback === "function") {
                        callback();
                    }
                });
        };

        ePlus.modules.prc.saveUserRequestOrder("POST", userRequestOrder, onSuccess);
    }

    var sendUserRequestOrderLineItemById = function (orderType, status, lineItemStatus, lineItemId, orgId, userId) {
        var lineItem = getLineItem(lineItemId);

        lineItem.attributes = [{
            userRequestOrderLineItemId: lineItem.UserRequestOrderLineItemId,
            type: 4,
            value: lineItem.Shipping.MethodDescription,
            auditOrgId: orgId,
            auditUserId: userId
        }];

        var distributor = {
            id: lineItem.DistributorId
        };

        sendUserRequestOrderLineItem(orderType, status, lineItemStatus, lineItem, distributor, orgId, userId);
    }

    var sendSelectedUserRequestOrderLineItems = function (orderType, status, lineItemStatus, orgId, userId) {
        var lineItems = buildLineItem(lineItemStatus);

        if (_.isNil(lineItems) || lineItems.length === 0) {
            alert(res.getRes("select_at_least_one_title"));
            return;
        }

        var today = new Date();
        var userRequestOrderLineItemId = lineItems[0].userRequestOrderLineItemId;
        var distributor = {
            id: lineItems[0].DistributorId
        };

        var userRequestOrder = {
            userRequestOrderId: 0,
            name: today.toISOString(),
            type: orderType,
            status: status,
            distributor: distributor,
            auditOrgId: orgId,
            auditUserId: userId
        };

        var onSuccess = function (newUserRequestOrder) {
            _.forEach(lineItems, function (item, i) {
                item.UserRequestOrderId = newUserRequestOrder.userRequestOrderId;
                item.attributes = [{
                    userRequestOrderLineItemId: item.UserRequestOrderLineItemId,
                    type: 4,
                    value: item.Shipping.MethodDescription,
                    auditOrgId: orgId,
                    auditUserId: userId
                }];
            });

            window.ePlus.modules.prc.savePrcRequest("PATCH", null, lineItems,
                function () {
                    var userRequestOrderLineItemIds = _.map(lineItems, function(li) {
                        return li.UserRequestOrderLineItemId;
                    });
                    removeListViewItemsAndUpdateListView(userRequestOrderLineItemIds);
                    _.forEach(userRequestOrderLineItemIds, function (id) {
                        $("#as_" + id).slideUp();
                    });

                    updateDashboardValue();
                    closeModal();
                });
        };

        ePlus.modules.prc.saveUserRequestOrder("POST", userRequestOrder, onSuccess);
    }

    function buildLineItem(lineItemStatus) {
        var userRequestOrderLineItemIds = window.getSelectedItems();
        var lineItems = [];

        _.forEach(userRequestOrderLineItemIds, function (id, i) {
            var lineItem = getLineItem(id);

            if (!lineItem.ApprovedQty) {
                lineItem.ApprovedQty = lineItem.RequestedQty;
            }
            lineItem.Status = lineItemStatus;
            lineItems.push(lineItem);
        });

        return lineItems;
    }

    function updateDashboardValue() {
        GetDashboardValuePlusRefresh(util.getEnumValue("resultType", "PRCSOPEN"), util.getEnumValue("dashType", "DASHDRC"));
        GetDashboardValuePlusRefresh(util.getEnumValue("resultType", "PRCSUNSENT"), util.getEnumValue("dashType", "DASHDRC"));
        GetDashboardValuePlusRefresh(util.getEnumValue("resultType", "PRCSSENT"), util.getEnumValue("dashType", "DASHDRC"));
        GetDashboardValuePlusRefresh(util.getEnumValue("resultType", "PRCSDECLINED"), util.getEnumValue("dashType", "DASHDRC"));
    }

    return {
        removeLineItemFromUserRequestOrder: removeLineItemFromUserRequestOrder,
        saveUserRequestOrderLineItem: saveUserRequestOrderLineItem,
        sendUserRequestOrderLineItemById: sendUserRequestOrderLineItemById,
        enableUserRequestOrderApprovedQuantity: enableUserRequestOrderApprovedQuantity,
        disableUserRequestOrderApprovedQuantity: disableUserRequestOrderApprovedQuantity,
        sendSelectedUserRequestOrderLineItems: sendSelectedUserRequestOrderLineItems,
        sendUserRequestOrderLineItem: sendUserRequestOrderLineItem,
        setLineItemStatus: setLineItemStatus
    }
})();;
ePlus.modules.leftNav = new (function () {
    var self = this;

    var SmallScreenWidth = 1100;
    var LeftNavFixedPosition = "fixed";

    this.initialize = function(doCollapseLeftNavBar, pageWidth, leftNavPreference) {
        if (doCollapseLeftNavBar) {
            self.closeLeftNav();
        } else {
            if (pageWidth < SmallScreenWidth) {
                self.closeLeftNav();
            } else {
                $("#leftNavLock").show();
                if (leftNavPreference === LeftNavFixedPosition) {
                    self.toggleFixedLeftNav(false);
                }
            }
        }
    };

    this.toggleFixedLeftNav = function (doSaveTogglePreference) {
        if ($("#leftNav").hasClass("leftNavRelative")) {
            self.fixLeftNav(doSaveTogglePreference);
        } else {
            self.unFixLeftNav(doSaveTogglePreference);
        }
    };

    this.openLeftNav = function (doSaveLeftNavPreference, dashType) {
        if (!$("#leftNav").hasClass("leftNavContracted")) {
            return;
        }
        var $leftNav = $("#leftNav");
        $leftNav.removeClass("leftNavContracted");
        $(".leftContract", $leftNav).hide();
        $(".leftExpand", $leftNav).show();
        $('body').css("margin-left", "0");
        $("#leftNavContainer").removeClass("leftNavHeaderClosed");
        if ($(window).width() > 1100) {
            $("#leftNavLock").show();
            if (getListViewProperty("fixedNavPref") !== LeftNavFixedPosition) {
                self.unFixLeftNav();
            } else {
                self.fixLeftNav();
            }
        } else {
            $("#leftNavLock").hide();
            self.fixLeftNav();
            $('body').css("margin-left", "0");
            $("#leftNavContainer").addClass("leftNavHeaderOpen");
        }
        window.toggleNoRefinementsMessage();
        if (doSaveLeftNavPreference) {
            self.saveCollapseLeftNavBarPreference(false, dashType);
        }
    };

    this.closeLeftNav = function (doSaveLeftNavPreference, dashType) {
        if ($("#leftNav").hasClass("leftNavContracted")) {
            return;
        }
        var $leftNav = $("#leftNav");
        $leftNav.removeClass("leftNavRelative leftNavFixed").addClass("leftNavContracted");
        $(".leftContract", $leftNav).show();
        $(".leftExpand", $leftNav).hide();
        $('body').css("margin-left", "0");
        $("#leftNavContainer").removeClass("leftNavHeaderOpen").addClass("leftNavHeaderClosed");
        $("#contentMain").removeClass("contentNavHeaderFlush contentNavHeaderMargin")
            .addClass("contentNavHeaderFull");
        $('.app-header').css('width', '100%');
        if (doSaveLeftNavPreference &&
            typeof self.saveCollapseLeftNavBarPreference === "function") {
            self.saveCollapseLeftNavBarPreference(true, dashType);
        }
    };

    this.fixLeftNav = function (doSavePreference) {
        $("#leftNav").removeClass("leftNavRelative").addClass("leftNavFixed");
        $("#leftNavLock").removeClass("icon-pin").addClass("icon-chevron-down");
        $("#leftNavHeader").css("margin-top", "0");
        if (window.innerWidth > SmallScreenWidth) {
            $('body').css("margin-left", "260px");
            $('.app-header').css('width', (window.innerWidth - 260) + 'px');
        }
        $("#contentMain").removeClass("contentNavHeaderFull contentNavHeaderMargin")
            .addClass("contentNavHeaderFlush");
        if (doSavePreference) {
            window.setListViewProperty("fixedNavPref", LeftNavFixedPosition);
            savePreference('navigation', 'leftNav', LeftNavFixedPosition);
        }
    };

    this.unFixLeftNav = function (doSavePreference) {
        $("#leftNav").addClass("leftNavRelative").removeClass("leftNavFixed");
        $("#leftNavLock").addClass("icon-pin").removeClass("icon-chevron-down");
        $("#leftNavHeader").css("margin-top", "28px");
        if (window.innerWidth > SmallScreenWidth) {
            $('body').css("margin-left", "0");
            $('.app-header').css('width', '100%');    
            $("#leftNavContainer").removeClass("leftNavHeaderOpen").addClass(" leftNavHeaderClosed");
            $("#contentMain").removeClass("contentNavHeaderFlush contentNavHeaderFull")
                .addClass("contentNavHeaderMargin");
        }
        if (doSavePreference) {
            window.setListViewProperty("fixedNavPref", '');
            savePreference('navigation', 'leftNav', '');
        }
    };

    this.saveCollapseLeftNavBarPreference = function (isCollapsed, dashType) {
        var prefType = "display";
        var prefName = "collapseLeftNavBar_" + dashType;
        savePreference(prefType, prefName, isCollapsed);
    };

    this.isRefinementTheCurrentTempCategory = function(filterType, refinement) {
        var tempCategoryFilter = window.EdelweissAnalytics.getCurrentTemporaryCategoryFilterObject();
        return window.EdelweissAnalytics.isLeftNavCategoryFilterType(filterType)
                && !_.isEmpty(tempCategoryFilter) && refinement === tempCategoryFilter.categoryName
            && refinement !== window.EdelweissAnalytics.noCategoryLabel;
    };

    this.selectTempCategoryInLeftNavIfRefinementIsCurrentTempCategory = function(filterType, refinement, $filterCheckbox) {
        if (self.isRefinementTheCurrentTempCategory(filterType, refinement)) {
            $filterCheckbox.removeClass("box_unchecked").addClass("box_checked");
        }
    };

    this.clearRefinementsOnAnalyticsHome = function (filterType) {
        var appliedRefinements = ePlus.modules.listView.refinements.apply.buildAppliedRefinements();
        var refinementsToRemove = appliedRefinements.filter(function (refinement) {
            return typeof filterType === "undefined" || refinement.type == filterType;
        });

        refinementsToRemove.forEach(function (refinement) {
            var refinementValue = getRefineMapValue(refinement);
            if (self.isRefinementTheCurrentTempCategory(refinement.type, refinementValue)) {
                window.EdelweissAnalytics.removeTemporaryCategoryFilter();
            }
        });

        var visibleDashTypes = window.EdelweissAnalytics.getDashTypesOfVisibleLanes();
        visibleDashTypes.forEach(function (dashType) {
            if (window.EdelweissAnalytics.laneKeyByDashType.hasOwnProperty(dashType)) {
                var laneKey = window.EdelweissAnalytics.laneKeyByDashType[dashType];
                refinementsToRemove.forEach(function (refinement) {
                    var refinementValue = getRefineMapValue(refinement);
                    window.EdelweissAnalytics.removeRefinementFromAttributeFilters(dashType, refinement.type,
                        refinementValue);
                });
                if (laneKey === window.EdelweissAnalytics.LaneKeys.TrendsAnalysis) {
                    window.EdelweissAnalytics.isTrendsAnalysisChartUpdated = true;
                }
                window.EdelweissAnalytics.startLaneUpdateProcess(laneKey);
            }
        });
    };

    var getRefineMapValue = function (refinement) {
        var refinementValue = null;
        if (ePlus.modules.listView.refinements.isMultiValueRefinement(refinement.type)) {
            refinementValue = refinement.value;
        } else {
            if (typeof window.refineMap !== "undefined" && window.refineMap !== null
                && window.refineMap.hasOwnProperty(refinement.type)
                && !isNaN(refinement.value) && parseInt(refinement.value) < window.refineMap[refinement.type].length) {
                refinementValue = window.refineMap[refinement.type][parseInt(refinement.value)];
            }
        }
        return refinementValue;
    };
})();
;
; window.ePlus.modules.welcome = (function () {
    var ui = window.ePlus.ui;
    var urlHelper = window.ePlus.util.urlHelper;
    var $welcome;
    var $login;
    var $registration;

    var goToLogin = function () {
        window.scrollTo(0, 0);
        $registration.addClass('hidden');
        $login.removeClass('hidden');
    };

    var goToRegistration = function () {
        window.scrollTo(0, 0);
        $login.addClass('hidden');
        $registration.removeClass('hidden');
    };

    var initializeControl = function () {
        $welcome = $('.welcome');
        $login = $('.login', $welcome);
        $registration = $('.registration', $welcome);

        $('.registration-control', $registration)
            .load('/GetTreelineControl.aspx?controlName=/uc/registration/RegisterOne.ascx');
    };

    var initializeRegistrationClickEventHandler = function () {
        $('#register').on('click', function (e) {
            var url  = urlHelper.getReturnLoginUrl(window.location.hash);
            history.pushState(null, null, url);

            goToRegistration();
            return false;
        });
    };

    var initializeBrowseClickEventHandler = function () {
        $('#browse').on('click', function () {
            history.pushState({
                source: 'welcome'
            }, null, '/');

            ui.clearInteriorPageContent();
            pageChange('#dashboard');
            return false;
        });
    };

    var initializeControlEventHandlers = function () {
        initializeRegistrationClickEventHandler();
        initializeBrowseClickEventHandler();
    };

    var initialize = function (options) {
        initializeControl();
        initializeControlEventHandlers();

        if (options && options.isSignUp) {
            $('#register').trigger('click');
        }
    };

    return {
        initialize: initialize,
        goToLogin: goToLogin,
        goToRegistration: goToRegistration
    };
})();;
ePlus.modules.savedFilterDisplay = (function () {
    var res = ePlus.resources;

    var initSavedFilterDisplay = function () {
        if ($("#activeFilters").val() > 0) {
            $("#noFilterMessage").hide();
        } else {
            $("#noFilterMessage").show();
        }

        $("#savedFilterActions").show();
        $("#savedFilterShare").show();
        if ($("#filterShareCheckbox").hasClass("box_checked")) {
            $("#filterShareCheckbox").removeClass("box_checked");
            $("#filterShareCheckbox").addClass("box_unchecked");
        }
        $("#savedFilterDelete").show();
        $("#savedFilterEdits").show();
        $("#selectFiltersHeader").html(res.getRes('select_filters_below'));
        $("#filter_Category").show();
        $("#sharedFilterNoteWrap").hide();

        $('.closeFilter').on('click', function () {
            removeFilter($(this));
        });
    };

    var removeFilter = function (refineVal) {
        var filter = refineVal.attr("data-refine");
        var itemType = refineVal.attr("data-itemtype");
        //This removes the visual display of the element
        $(document.getElementById("fd_" + filter)).remove();
        $('#refine_' + filter).remove();
        $("#activeFilters_" + itemType).val($("#activeFilters_" + itemType).val() * 1 - 1);
        if ($("#activeFilters_" + itemType).val() === "0") {
            $("#filter_Row_" + itemType).hide();
            $("#activeFilters").val($("#activeFilters").val() * 1 - 1);
        }

        if ($("#activeFilters").val() === "0") {
            $("#noFilterMessage").show();
        }

        ePlus.modules.manageUserAffiliation.filter.setFilterDefinition();
    };

    return {
        initSavedFilterDisplay: initSavedFilterDisplay,
        removeFilter: removeFilter
    }
})();;
; window.ePlus.modules.login = (function () {
    var $loginForm;
    var $loginError;

    var initializeControl = function () {
        $loginForm = $('#login-form');
        $loginError = $('#login-error');
    };

    var initializeLoginFormSubmitEventHandler = function () {
        $loginForm.on('submit', function () {
            $loginError.addClass('hidden');

            var hashParams = $.deparam(window.location.hash.substr(1));

            appendReturnUrlToLoginForm(hashParams.returnUrl);

            var values = $(this).serializeJSON();

            $.post('/GetJSONData.aspx?builder=Login', values, function (data) {
                if (data && data.length > 0 && data[0].code === 'SUCCESS') {
                    checkUrlAndLogin(data[0].obj);
                } else {
                    $loginError.removeClass('hidden');
                }
            }, 'json');

            return false;
        });
    };

    var appendReturnUrlToLoginForm = function (returnUrl) {
        var $returnUrl = $('<input />', {
            type: 'hidden',
            name: 'redirectUriOrHash',
            value: returnUrl
        });

        $loginForm
            .remove('input[name=redirectUriOrHash]')
            .append($returnUrl)
    }

    var initializeForgotPasswordClickEventHandler = function () {
        $('#forgot-password').on('click', function () {
            openForgotPassword();
            return false;
        })
    };

    var initializeControlEventHandlers = function () {
        initializeLoginFormSubmitEventHandler();
        initializeForgotPasswordClickEventHandler();
    };

    var initialize = function (options) {
        initializeControl();
        initializeControlEventHandlers();

        if (options && options.isForgotPassword) {
            history.replaceState(null, null, '/#Login');
            $('#forgot-password').trigger('click');
        }
    };

    return {
        initialize: initialize
    };
})();;
;
ePlus.modules.listView.reviews = (function () {
    var initializeReviewsListView = function () {
        $('.reviewAssessmentNotice', '#itemContainer').webuiPopover({
            trigger: 'hover',
            container: '#pageContent',
            width: 350
        });
    };

    return {
        initializeReviewsListView: initializeReviewsListView
    }
})();;
;
ePlus.modules.listView.reviewsCopies = (function () {
    var initializeFeaturedTitlesInset = function () {
        if (!doLoadFeaturedTitlesInset()) return;

        clearFeaturedTitlesInset();

        $('#featured-titles-inset-con').load('/FeaturedTitles/Inset');
    };

    var doLoadFeaturedTitlesInset = function () {
        return isFeaturedTitlesResultType() && getCurrentPageNumber() === 1 && $('#itemContainer').length > 0;
    }

    var isFeaturedTitlesResultType = function () {
        return getListViewProperty('resultType') === getEnumValue('resultType', 'TITLEDRCAVAILABLE')
            || getListViewProperty('resultType') === getEnumValue('resultType', 'TITLEDRCREQUESTABLE')
            || getListViewProperty('resultType') === getEnumValue('resultType', 'TITLEDRCDOWNLOADABLE');
    }

    return {
        initializeFeaturedTitlesInset: initializeFeaturedTitlesInset
    }
})();;
ePlus.modules.notificationPreferences = (function () {
    function initialize() {
        addFrequencyOptionSelectionListener();
        addViewNotificationsClickListener();
        $("#notification-preference-label").webuiPopover({
            url: "#notification-preference-selector",
            width: "200px",
            placement: 'top-left',
        });
    }

    function loadNotificationSummary() {
        $('#batch-notification-summary')
            .html('<i>' + getRes('loading_email_notification_frequency') + '...</i>')
            .load('/GetTreelineControl.aspx?controlName=/uc/support/notificationPreferences.ascx');
    }

    function addFrequencyOptionSelectionListener() {
        $(".laneEmailNotificationFrequencyOptionBox").on("click", function () {
            var $this = $(this);
            if (window.ePlus.ui.isRadioChecked($this)) return;
            uncheckAllRadioButtons();
            window.ePlus.ui.makeRadioChecked($this);
            saveFrequencyOption($this.attr("data-id"));
        });
    }

    function addViewNotificationsClickListener() {
        $('#view-notifications').on('click', function () {
            window.openMessageCenter(0, 0);
        });
    }

    function saveFrequencyOption(frequencyOptionId) {
        $.ajax({
            type: "POST",
            url: "api/v1/subscriptions/homepageLane/" + window.getEnumValue("resultType", "NOTICESALLNEW")
                + "/frequency/" + frequencyOptionId,
            success: function () {
                loadNotificationSummary();
                alert(window.getRes("changes_saved"));
            },
            error: function () {
                alert(window.getRes("error_unexpected"));
            }
        });
    }

    function uncheckAllRadioButtons() {
        $(".laneEmailNotificationFrequencyOptionBox").each(function (_, elm) {
            window.ePlus.ui.makeRadioUnchecked($(elm));
        });
    }

    function initializeMarkAllNoticesAsRead(targetElementId, successCallback) {
        $('#' + targetElementId).off().click(function () {
            $.ajax({
                type: 'PUT',
                url: "api/me/notifications/markAsRead"
            })
                .done(function () {
                    if (typeof successCallback === 'function') {
                        successCallback();
                    }
                });
        })
    }

    return {
        initialize: initialize,
        loadNotificationSummary: loadNotificationSummary,
        initializeMarkAllNoticesAsRead: initializeMarkAllNoticesAsRead
    }
})();
;
window.ePlus.modules.manageAffiliationImages = (function () {
    var LOGO = 100;
    var INLINELOGO = 101;

    var savingModalControl = 'popModal_inner';

    var res = window.ePlus.resources;

    var initUploadForm = function (fileUploadId, uploadFormId, uploadFormFileId, affiliationId, imageType, imageMaxWidth, imageMaxHeight) {
        $(fileUploadId).fileupload({
            url: '/api/affiliations/' + affiliationId + '/logos/' + imageType,
            xhrFields: {
                withCredentials: true
            },
            dataType: 'json',
            imageMaxWidth: imageMaxWidth,
            imageMaxHeight: imageMaxHeight,
            add: function (e, data) {
                savingModalOverlay(res.getRes('saving'), savingModalControl);

                if (data && data.files && data.files.length && data.files[0].name) {
                    addFileNameToUploadList(this, e, data, uploadFormFileId);
                } else {
                    alert(res.getRes('error_unexpected'));
                }
            },
            done: function (e, data) {
                closeSavingModalOverlay(savingModalControl);
                if (data) {
                    if (data._response && data._response.jqXHR && data._response.jqXHR.responseText) {
                        var responseTextJson = JSON.parse(data._response.jqXHR.responseText);
                        updateImage(affiliationId, imageType, responseTextJson);
                    }

                    showUploadForm($(uploadFormId));
                    clearUploadFileList($(uploadFormFileId));
                } else {
                    alert(res.getRes('error_unexpected'));
                }
            },
            fail: function (e, data) {
                closeSavingModalOverlay(savingModalControl);
                if (data && data._response && data._response.jqXHR && data._response.jqXHR.responseText) {
                    var responseTextJson = JSON.parse(data._response.jqXHR.responseText);
                    alert(responseTextJson.message);
                } else {
                    alert(res.getRes('error_unexpected'));
                }

                clearUploadFileList($(uploadFormFileId));
            }
        });
    };

    function addFileNameToUploadList(self, e, data, jqueryIdSelector) {
        var fileName = data.files[0].name;
        var message = res.getRes('uploading') + fileName + '...';

        data.context = $('<li/>').text(message).appendTo(jqueryIdSelector);

        $.blueimp.fileupload.prototype.options.add.call(self, e, data);
    }

    function clearUploadFileList($uploadFiles) {
        if ($uploadFiles) {
            $uploadFiles.html("");
        }
    }

    function showUploadForm($form) {
        $form.removeClass('hidden');
    }

    function updateImage(affiliationId, imageType, responseText) {
        var imageSrc = responseText + "?t=" + new Date().getTime();
        if (imageType === LOGO) {
            updateListViewImage(affiliationId, imageSrc);
        }

        var $previewElementPrefix = getPreviewJqueryElement(affiliationId, imageType);
        $previewElementPrefix.attr('src', responseText);

        var $deleteImageElement = getDeleteImageJqueryElement(imageType);
        $deleteImageElement.removeClass('hidden');
    }

    function updateListViewImage(affiliationId, imageSrc) {
        var $row = $('#as_' + affiliationId);
        $row.find('div.affil-no-logo').remove();

        var $logoElement = $('img.affil-logo', $row);
        if ($logoElement.length === 0) {
            $('.affiliation-logo-column', $('#as_' + affiliationId)).html('<img src="' + imageSrc + '&width=150&height=150&scale=both" />');
        } else {
            $logoElement.attr('src', imageSrc + '&width=150&height=150&scale=both');
        }
    }

    var initImageDelete = function (elementId, affiliationId, imageType) {
        $('#' + elementId).off('click').on('click', function () {
            $.ajax({
                type: 'DELETE',
                url: '/api/affiliations/' + affiliationId + '/logos/' + imageType,
            })
                .done(function () {
                    removeImage(affiliationId, imageType);
                })
                .fail(function () {
                    alert(res.getRes('error_unexpected'));
                });
        });
    }

    function removeImage(affiliationId, imageType) {
        if (imageType === LOGO) {
            $('.affiliation-logo-column', $('#as_' + affiliationId)).html('<div class="icon-groups affil-no-logo"></div>');
        }

        var $previewElementPrefix = getPreviewJqueryElement(affiliationId, imageType);
        $previewElementPrefix.attr('src', '');

        var $deleteImageElement = getDeleteImageJqueryElement(imageType);
        $deleteImageElement.addClass('hidden');
    }

    function getPreviewJqueryElement(affiliationId, imageType) {
        var previewElementPrefix = imageType === LOGO ? "#main-preview-" : "#inline-preview-";
        return $(previewElementPrefix + affiliationId);
    }

    function getDeleteImageJqueryElement(imageType) {
        var deleteImageId = imageType === LOGO ? '#delete-main-logo' : '#delete-inline-logo';
        return $(deleteImageId);
    }

    return {
        initUploadForm: initUploadForm,
        initImageDelete: initImageDelete
    }
})();;
ePlus.modules.createNotice = (function () {
    var init = function (appUserId, maxCharacterCount) {
        $('#send-notice').on('click', function () {
            var message = $('#notice-message').val();

            if (message.length === 0) {
                alert(getRes('enter_a_message'));
                return;
            } 

            var user = {
                appUserId: appUserId,
                message: message
            }

            $.ajax({
                type: 'POST',
                url: '/api/v1/users/messages',
                data: user
            })
                .done(function () {
                    alert(getRes('message_was_sent'));
                    closeMultiModal('send-notice-modal');                 
                })
                .fail(function () {
                    closeMultiModal('send-notice-modal');
                });
        });

        $('#notice-message').on('keyup', function () {
            var remainingCharacters = maxCharacterCount - $('#notice-message').val().length;
            $('#characterCount').html(remainingCharacters);
        });
    }

    return {
        init: init
    }
})();;
;
window.ePlus.modules.adminTechnicalContact = (function () {
    var deleteContact = function (orgId, callback) {
        $.ajax({
            type: 'DELETE',
            url: 'api/organization/' + orgId + '/contacts/admins/technical'
        }).done(function () {
            if (typeof (callback) === 'function') {
                callback();
            }
        });
    };

    return {
        deleteContact: deleteContact
    };
})();;
window.ePlus.modules.e360.campaigns = (function() {
  var config = {
      uri: null,
      skus: {}
  };

  var enableCampaignAdd = function(passedConfig) {
      config = passedConfig;
      getCampaignsBySku(config.skus);
      getCampaignTitleRates(config.skus);
    $(".add-to-campaign").each(function() {
      var $self = $(this);
      var sku = $self.attr("data-sku");
      $self.webuiPopover({
        type: "async",
        cache: false,
        closeable: true,
        container: "#pageContent",
        url: "campaigns/active/" + sku,
        placement: "left",
        width: "400px",
        height: "410px",
        async: {
          success: function() {}
        }
      });
    });
  };

  var getCampaignsBySku = function(skus) {
      $.ajax({
          type: "POST",
          url: config.uri + "api/e360Mobile/campaigns/bySku",
          dataType: "json",
          contentType: "application/json; charset=utf-8",
          data: JSON.stringify(skus),
          crossDomain: true,
          xhrFields: {
              withCredentials: true
          }
      })
          .done(function (data) {
              for (var i = 0; i < skus.length; i++) {
                  var sku = skus[i];
                  var activeCount = 0;
                  var sentCount = 0;
                  if (sku in data) {
                      var campaigns = data[sku];
                      for (var j = 0; j < campaigns.length; j++) {
                          if (campaigns[j].status == "active") {
                              activeCount += 1;
                          }
                          if (campaigns[j].status == "sent") {
                              sentCount += 1;
                          }
                      }
                      if (activeCount > 0) {
                          $("#cp-active-" + sku).html(activeCount);
                      }
                      if (sentCount > 0) {
                          $("#cp-sent-" + sku).html(activeCount);
                      }
                  }

                  $("#cp-active-frame-" + sku).toggleClass("hidden", activeCount === 0);
                  $("#cp-sent-frame-" + sku).toggleClass("hidden", sentCount === 0);
              }
          });
    };

    var getCampaignTitleRates = function (skus) {
        $.ajax({
            type: "POST",
            url: config.uri + "api/e360Mobile/fees/ccmRates",
            dataType: "json",
            contentType: "application/json; charset=utf-8",
            data: JSON.stringify(skus),
            crossDomain: true,
            xhrFields: {
                withCredentials: true
            }
        })
            .done(function (data) {
                for (var i = 0; i < data.length; i++) {
                    var sku = data[i].sku;
                    var rate = data[i].rate;
                    $("#pub-ccm-frame-" + sku).removeClass("hidden");
                    if (rate === 1) {
                        $("#pub-ccm-rate-" + sku).html('$');
                    } else {
                        $("#pub-ccm-rate-" + sku).html(rate + 'X');
                    }
                }
            });
    };

    var getActiveCampaigns = function (callback) {
        var activeCampaignStatus = getEnumValue("campaignStatusType", "ACTIVE")
        $.ajax({
            type: "GET",
            url: config.uri + "/api/e360Mobile/campaigns/?status=" + activeCampaignStatus,
            xhrFields: {
                withCredentials: true
            }
        }).done(function (data) {
            if (typeof callback === "function") {
                callback(data);
            }
        }).fail(function() {
            alert(window.getRes("error_unexpected"));
        });
  };

  var getCampaignsThisTitleIsOn = function(activeCampaigns, sku) {
    var skus = [sku];
    $.ajax({
      type: "POST",
      url: config.uri + "api/e360Mobile/campaigns/bySku",
      dataType: "json",
      contentType: "application/json; charset=utf-8",
      data: JSON.stringify(skus),
      crossDomain: true,
      xhrFields: {
        withCredentials: true
      }
    })
      .done(function(data) {
        var currentCampaigns = data && sku in data && data[sku].map(function(c) {
          return c.campaignId;
        });
        renderActiveCampaignTable(activeCampaigns, currentCampaigns, sku);
      })
      .fail(function() {
      });
  };

  var renderActiveCampaignTable = function(
    activeCampaigns,
    currentCampaigns,
    sku
  ) {
    var dHtml = "";
    var activeCampaignCount = activeCampaigns ? activeCampaigns.length : 0;
    for (var i = 0; i < activeCampaignCount; i++) {
        var campaign = activeCampaigns[i];
        var itemCount = campaign.itemFilters
            ? campaign.itemFilters.entityFilters.length
            : 0;
        var active = currentCampaigns ? currentCampaigns.indexOf(campaign.campaignId) : -1;
        dHtml +=
          '<tr class="title-2-detail-item view-row" data-row="' +
          campaign.campaignId +
          '">';
        dHtml +=
          '<td class="active-campaign-cell" data-sort="' +
          campaign.createdDate +
          '">';
        dHtml +=
          '<div class="column posDisplay superScriptStripNumber active-campaign-count" title="' +
          window.getRes("items_in_campaign") +
          '">';
        dHtml +=
          '<span id="active-count-' +
          campaign.campaignId +
          '">' +
          itemCount +
          "</span>";
        dHtml += "</div>";
        dHtml +=
          '<div class="column-narrow dotDot campaign-name" title="' +
          campaign.name +
          '">' +
          campaign.name +
          "</div>";
        dHtml += '<div class="clear"></div>';
        dHtml += "</td>";
        dHtml += "<td>";

        dHtml +=
          '<span id="add-to-' +
          campaign.campaignId +
          '" onClick="window.ePlus.modules.e360.campaigns.addTitleToCampaign(' +
          campaign.campaignId +
          ",'" +
          sku +
          '\');" class="ant-button-small';
        if (active > -1) {
          dHtml += " hidden";
        }
        dHtml += '">' + window.getRes("add_to") + "</span>";

        dHtml +=
          '<span id="remove-from-' +
          campaign.campaignId +
          '" onClick="window.ePlus.modules.e360.campaigns.removeTitleFromCampaign(' +
          campaign.campaignId +
          ",'" +
          sku +
          '\');" class="ant-button-small';
        if (active === -1) {
          dHtml += " hidden";
        }
        dHtml += '">' + window.getRes("remove_from") + "</span>";

        dHtml += "</td>";
        dHtml += "</tr>";
      }

      if (activeCampaignCount === 0) {
          dHtml = "<tr><td colspan='2'><span class='italic'>" + window.getRes('no_active_campaigns') + "</span></td></tr>";
      }

    $("#active-campaigns").append(dHtml);

    $("#active-campaigns").DataTable({
      lengthChange: false,
      pageLength: 5,
      order: [[0, "desc"]],
      pagingType: "simple",
      columnDefs: [{ orderable: false, targets: [0, 1] }],
      fnDrawCallback: function() {
        if (activeCampaignCount < 6) {
          $(".dataTables_paginate", $("#action-campaign")).addClass("hidden");
          $(".dataTables_info", $("#action-campaign")).addClass("hidden");
        }
      }
    });
    };

    var toggleNewCampaignView = function () {
        $("#create-new").toggleClass("hidden");
        $("#select-existing").toggleClass("hidden");
        $("#create-new-button").toggleClass("hidden");
    }

    var createNewCampaignClick = function() {
        toggleNewCampaignView();
        $("#new-campaign-input").focus();
    };

  var createNewCampaign = function(sku) {
    var campaignName = $("#new-campaign-input").val();
    if (campaignName != "") {
      var campaign = {
        status: 1,
        name: encodeURIComponent(campaignName)
      };

      $.ajax({
        type: "POST",
        url: config.uri + "/api/e360Mobile/campaigns",
        dataType: "json",
        contentType: "application/json; charset=utf-8",
        data: JSON.stringify(campaign),
        xhrFields: {
          withCredentials: true
        }
      })
        .done(function() {
          addItemFilterToNewCampaign(campaignName, sku);
        })
        .fail(function() {
          alert(window.getRes("error_unexpected"));
        });
    } else {
      alert(window.getRes("enter_name_for_campaign"));
    }
    };

    var createNewCampaignForBulkAdd = function (skus) {
        var campaignName = $("#new-campaign-input").val();

        if (campaignName != "") {
            var campaign = {
                status: 1,
                name: encodeURIComponent(campaignName)
            };

            $.ajax({
                type: "POST",
                url: config.uri + "/api/e360Mobile/campaigns",
                dataType: "json",
                contentType: "application/json; charset=utf-8",
                data: JSON.stringify(campaign),
                xhrFields: {
                    withCredentials: true
                }
            }).done(function () {
                toggleNewCampaignView();
                addItemFiltersToNewCampaign(skus);
            }).fail(function () {
                alert(window.getRes("error_unexpected"));
            });
        } else {
            alert(window.getRes("enter_name_for_campaign"));
        }
    }

  var addItemFilterToNewCampaign = function(campaignName, sku) {
    var entityFilter = {
      entityType: "product",
      entityId: sku
    };

    $.ajax({
      type: "PUT",
      url: config.uri + "/api/e360Mobile/campaigns/latest/filters/items",
      dataType: "json",
      contentType: "application/json; charset=utf-8",
      data: JSON.stringify(entityFilter),
      xhrFields: {
        withCredentials: true
      }
    })
      .done(function() {
        var skus = [sku];
        getCampaignsBySku(skus);
        $(".add-to-campaign").webuiPopover("hide");
      })
      .fail(function() {
        alert(window.getRes("error_unexpected"));
      });
    };

    var addItemFiltersToNewCampaign = function (skus) {
        var entities = [];

        for (var i in skus) {
            var sku = skus[i];

            entities.push({
                entityType: "product",
                entityId: sku
            })
        }

        $.ajax({
            type: "POST",
            url: config.uri + "/api/e360Mobile/campaigns/latest/filters/items/bulk",
            dataType: "json",
            contentType: "application/json; charset=utf-8",
            data: JSON.stringify(entities),
            xhrFields: {
                withCredentials: true
            }
        }).done(function (ret) {
            reloadBulkAddTitles();
        }).fail(function (ret) {
            alert(window.getRes("error_unexpected"));
        });
    }

  var addTitleToCampaign = function(campaignId, sku) {
    var entityFilter = {
      entityType: "product",
      entityId: sku
    };

    toggleActions(campaignId);
    $("#active-count-" + campaignId).html(
      $("#active-count-" + campaignId).html() * 1 + 1
    );
    $.ajax({
      type: "PUT",
      url:
        config.uri +
        "/api/e360Mobile/campaigns/" +
        campaignId +
        "/filters/items",
      dataType: "json",
      contentType: "application/json; charset=utf-8",
      data: JSON.stringify(entityFilter),
      xhrFields: {
        withCredentials: true
      }
    })
      .done(function() {
        var skus = [sku];
        getCampaignsBySku(skus);
      })
      .fail(function() {
        alert(window.getRes("error_unexpected"));
      });
    };

    var setBulkAddToCampaignsMessage = function (message) {
        $('#bulk-add-campaigns-message').html(message);
    }

    var clearBulkAddToCampaignsMessage = function () {
        setBulkAddToCampaignsMessage('');
    };

    var addTitlesToCampaigns = function (campaignIds, skus) {
        clearBulkAddToCampaignsMessage();

        if (campaignIds.length === 0) {
            setBulkAddToCampaignsMessage(getRes('bulk_add_no_campaigns_selected'));
            return;
        }

        var entities = [];

        for (var i in skus) {
            var sku = skus[i];

            entities.push({
                entityType: "product",
                entityId: sku
            })
        }

        var data = {
            CampaignIds: campaignIds,
            EntityFilters: entities
        };

        $.ajax({
            type: 'PUT',
            url: config.uri + '/api/e360Mobile/campaigns/filters/items/bulk',
            dataType: "json",
            contentType: "application/json; charset=utf-8",
            data: JSON.stringify(data),
            xhrFields: {
                withCredentials: true
            }
        }).done(function (ret) {
            setBulkAddToCampaignsMessage(getRes('bulk_add_to_campaign_success'));
            reloadBulkAddTitles();
        }).fail(function (ret) {
            console.log(ret);
        })
    }

  var removeTitleFromCampaign = function(campaignId, sku) {
    toggleActions(campaignId);
    $("#active-count-" + campaignId).html(
      $("#active-count-" + campaignId).html() * 1 - 1
    );
    $.ajax({
      type: "DELETE",
      url:
        config.uri +
        "/api/e360Mobile/campaigns/" +
        campaignId +
        "/filters/item/product/" +
        sku,
      dataType: "json",
      contentType: "application/json; charset=utf-8",
      xhrFields: {
        withCredentials: true
      }
    })
      .done(function() {
        var skus = [sku];
        getCampaignsBySku(skus);
      })
      .fail(function() {
        alert(window.getRes("error_unexpected"));
      });
  };

  var toggleActions = function(campaignId) {
    $("#add-to-" + campaignId).toggleClass("hidden");
    $("#remove-from-" + campaignId).toggleClass("hidden");
  };

    var initializeActiveCampaignsDialog = function(sku) {
        $("#action-campaign").on("click", function(e) {
            e.stopPropagation();
        });
        $("#create-new-campaign-button").on("click", function(e) {
            window.ePlus.modules.e360.campaigns.createNewCampaign(sku);
        });
        $(".go-to-create-campaign").on("click", function(e) {
            window.ePlus.modules.e360.campaigns.createNewCampaignClick();
        });

        window.ePlus.modules.e360.campaigns.getActiveCampaigns(function (activeCampaigns) {
            getCampaignsThisTitleIsOn(activeCampaigns, sku);
        });
    };

    var renderBulkAddTitles = function (activeCampaigns) {
        var bulkAddTitlesDataTable = $("#bulk-add-campaigns-table").DataTable();
        var rows = [];


        for (var i = 0; i < activeCampaigns.length; i++) {
            var campaign = activeCampaigns[i];
            var itemCount = campaign.itemFilters
                ? campaign.itemFilters.entityFilters.length
                : 0

            var htmlId = 'bulk-add-campaign-' + campaign.campaignId;

            var firstRowItem = '<input id="' + htmlId + '" class="bulk-add-campaign" data-campaign-id="' + campaign.campaignId + '" type="checkbox" />';
            var secondRowItem = '';

            secondRowItem += '<label for="' + htmlId + '">';
            secondRowItem += '<span>&nbsp;' + campaign.name + '</span>';
            secondRowItem += '<span class="column posDisplay superScriptStripNumber active-campaign-count">' + itemCount + '</span>';
            secondRowItem += '</label>';

            var row = [firstRowItem, secondRowItem];

            rows.push(row);
        }

        bulkAddTitlesDataTable.clear().draw();
        bulkAddTitlesDataTable.rows.add(rows).draw();
    };

    var getSelectedCampaignIds = function () {
        var campaignIds = [];

        $('.bulk-add-campaign:checked').each(function () {
            var campaignId = $(this).attr('data-campaign-id');

            campaignIds.push(campaignId);
        });

        return campaignIds;
    };

    var submitBulkAddTitles = function () {
        var selectedCampaignIds = getSelectedCampaignIds();
        var skus = window.getSelectedItems();

        addTitlesToCampaigns(selectedCampaignIds, skus);
    };

    var reloadBulkAddTitles = function () {
        window.ePlus.modules.e360.campaigns.getActiveCampaigns(function (activeCampaigns) {
            renderBulkAddTitles(activeCampaigns);

            $(".go-to-create-campaign").on("click", function (e) {
                window.ePlus.modules.e360.campaigns.createNewCampaignClick();
            });

            $("#create-new-campaign-button").off().on("click", function (e) {
                var skus = window.getSelectedItems();

                window.ePlus.modules.e360.campaigns.createNewCampaignForBulkAdd(skus);
            });

            $('#bulk-add-campaigns-submit').off().on('click', submitBulkAddTitles);
        });
    };

    var initializeBulkAddTitles = function () {
        $("#bulk-add-campaigns-table").DataTable({
            lengthChange: false,
            pageLength: 7,
            order: [[0, "desc"]],
            pagingType: "simple",
            columnDefs: [{ orderable: false, targets: [0, 1] }]
        });

        reloadBulkAddTitles();
    };

  return {
    enableCampaignAdd: enableCampaignAdd,
    getActiveCampaigns: getActiveCampaigns,
    createNewCampaignClick: createNewCampaignClick,
    createNewCampaign: createNewCampaign,
    removeTitleFromCampaign: removeTitleFromCampaign,
    addTitleToCampaign: addTitleToCampaign,
    initializeActiveCampaignsDialog: initializeActiveCampaignsDialog,
      initializeBulkAddTitles: initializeBulkAddTitles,
      createNewCampaignForBulkAdd: createNewCampaignForBulkAdd
  };
})();
;
window.ePlus.modules.e360.events = (function () {
    var config = {
        uri: null
    };

    var enableEventAdd = function ($parent, passedConfig) {
        config = passedConfig;

        $('.add-to-event', $parent).each(function () {
            var $self = $(this);
            var sku = $self.attr('data-sku');

            $self.webuiPopover({
                type: 'async',
                cache: false,
                closeable: true,
                container: '#pageContent',
                url: 'events/active/' + sku,
                placement: "left",
                width: "400px",
                height: "410px",
                async: {
                    success: function () { }
                }
            })
        });
    };

    var getActiveEvents = function (orgId) {
        return $.ajax({
            type: 'GET',
            url: config.uri + '/api/events/org/' + orgId,
            xhrFields: {
                withCredentials: true
            }
        }).done(function (events) {
            var now = new Date();
            var activeEvents = events.filter(function (e) {
                return e.time && new Date(e.time.startTime) > now;
            });
            return activeEvents;
        }).fail(function () {
            alert(window.getRes('error_unexpected'));
        });
    };

    var getTitleCount = function (event) {
        return event.skus.length;
    }

    var renderActiveEventsTable = function (sku, activeEvents) {
        var hasActiveEvents = activeEvents && activeEvents.length > 0;

        var isThisTitleInEvent = function (event, sku) {
            return event.skus.some(function (s) { return s.sku === sku; });
        }

        var removeFrom = window.getRes("remove_from");
        var associateWith = window.getRes("associate_with");
        var html = '';
        var eventTime = event.time ? event.time.startTime : '';

        if (hasActiveEvents) {
            activeEvents.forEach(function (event) {
                html += '<tr data-row="' + event.id + '">';
                html += '   <td data-sort="' + eventTime + '">';
                html += '       <div class="column posDisplay superScriptStripNumber active-event-count" title="' + window.getRes("items_in_this_event") + '">';
                html += '           <span id="active-event-count-' + event.id + '">' + getTitleCount(event) + '</span>';
                html += '       </div>';
                html += '       <div class="column-narrow dotDot event-name" title="' + event.title + '">';
                html += '           <span>' + event.title + '</span>';
                html += '       </div>';
                html += '   </td>';
                html += '   <td>';

                if (isThisTitleInEvent(event, sku)) {
                    html += '<button id="remove-from-event-' + event.id + '" class="ant-button remove-from-event-btn" data-sku="' + sku + '" data-event-id="' + event.id + '">' + removeFrom + '</button>'
                    html += '<button id="add-to-event-' + event.id + '" class="ant-button add-to-event-btn hidden" data-sku="' + sku + '" data-event-id="' + event.id + '">' + associateWith + '</button>'
                } else {
                    html += '<button id="remove-from-event-' + event.id + '" class="ant-button remove-from-event-btn hidden" data-sku="' + sku + '" data-event-id="' + event.id + '">' + removeFrom + '</button>';
                    html += '<button id="add-to-event-' + event.id + '" class="ant-button add-to-event-btn" data-sku="' + sku + '" data-event-id="' + event.id + '">' + associateWith + '</button>';
                }

                html += '   </td>';
                html += '</tr>';
            });
        } else {
            html += '<tr><td colspan="2"><span class="italic">' + window.getRes('no_active_events') + '</span></td></tr>';
        }

        $("#active-events-body").html(html);

        $('#active-events').DataTable({
            lengthChange: false,
            pageLength: 5,
            order: [[0, "desc"]],
            pagingType: "simple",
            columnDefs: [{ orderable: false, targets: [0, 1] }],
            fnDrawCallback: function () {
                $('.add-to-event-btn').off().on('click', function () {
                    var $this = $(this);
                    var sku = $this.attr('data-sku');
                    var eventId = $this.attr('data-event-id');

                    addTitleToEvent(eventId, sku);
                });

                $('.remove-from-event-btn').off().on('click', function () {
                    var $this = $(this);
                    var sku = $this.attr('data-sku');
                    var eventId = $this.attr('data-event-id');

                    removeTitleFromEvent(eventId, sku);
                });
            }
        });
    };

    var initializeActiveEventsDialog = function (sku, orgId) {
        getActiveEvents(orgId).done(function (activeEvents) {
            renderActiveEventsTable(sku, activeEvents);
        }).fail(function () {
            alert(window.getRes('error_unexpected'));
        });
    };

    var getEvent = function (eventId) {
        return $.ajax({
            type: 'GET',
            url: config.uri + '/api/events/' + eventId,
            xhrFields: {
                withCredentials: true
            }
        }).fail(function () {
            alert(window.getRes('error_unexpected'));
        });
    }

    var updateEventCount = function (event) {
        $('#active-event-count-' + event.id).html(getTitleCount(event));
    }

    var $addToEventButton = function (eventId) {
        return $('#add-to-event-' + eventId);
    }

    var $removeFromEventButton = function (eventId) {
        return $('#remove-from-event-' + eventId);
    }

    var addTitleToEvent = function (eventId, sku) {
        getEvent(eventId).done(function (event) {
            event.skus.push({ sku });

            var onSuccess = function (event) {
                $addToEventButton(eventId).addClass('hidden');
                $removeFromEventButton(eventId).removeClass('hidden');

                updateEventCount(event);
            }

            updateEvent(event, onSuccess);
        }).fail(function () {
            alert(window.getRes('error_unexpected'));
        });
    }

    var removeTitleFromEvent = function (eventId, sku) {
        getEvent(eventId).done(function (event) {
            event.skus = event.skus.filter(s => s.sku !== sku);

            var onSuccess = function (event) {
                $addToEventButton(eventId).removeClass('hidden');
                $removeFromEventButton(eventId).addClass('hidden');

                updateEventCount(event);
            }

            updateEvent(event, onSuccess);
        }).fail(function () {
            alert(window.getRes('error_unexpected'));
        });
    }

    var updateEvent = function (event, onSuccess) {
        $.ajax({
            type: 'PUT',
            url: config.uri + '/api/events/' + event.id,
            xhrFields: {
                withCredentials: true
            },
            contentType: 'application/json',
            data: JSON.stringify(event)
        }).done(onSuccess).fail(function () {
            alert(window.getRes('error_unexpected'));
        });
    }

    return {
        initializeActiveEventsDialog: initializeActiveEventsDialog,
        enableEventAdd: enableEventAdd
    };
})();;
ePlus.modules.header = (function () {
    var urlHelper = window.ePlus.util.urlHelper;

    function initialize() {
        $('#marketChangeSection').click(function (e) {
            openMarketSelector('session');
        });
        var $headerPubThreeSixtyLink = $('#header-pub-three-sixty-link');
        if ($headerPubThreeSixtyLink.length > 0) {
            $headerPubThreeSixtyLink.click(function () {
                openCrmPage('central', 0, 'recent');
            });
        }
        var $headerRegisterLink = $('#header-register-link');
        if ($headerRegisterLink.length > 0) {
            $headerRegisterLink.click(function () {
                var url = urlHelper.getReturnLoginUrl(window.location.hash);
                history.pushState(null, null, url);
                loginRegisterSaveHash();
            });
        } else {
            getHeaderNoticeCount();
            getAvatar();
            $('#notices').click(function () {
                openMessageCenter(0);
            });
            $('#header-saved-filter').click(function () {
                pageChange('savedFilters');
            });
            $('#mainUserLink').webuiPopover({
                placement: 'bottom-left',
                type: "async",
                cache: false,
                width: "340px",
                url: "/GetTreelineControl.aspx?controlName=/uc/header/HeaderProfile.ascx",
            });
        }
    }
    function getHeaderNoticeCount() {
        async.retry(2, _getHeaderNoticeCount);
    }

    function _getHeaderNoticeCount(callback) {
        $.ajax({
            type: "GET",
            cache: false,
            url: "api/me/notifications",
            contentType: "application/json",
            success: function (data) {
                var count = data.userNotices.filter(function (un) { return !un.status; }).length;
                count > 0 && $('#headerNoticeCount')
                    .html(count > 99 ? '99+' : count)
                    .removeClass('hidden');
                callback(null);
            },
            error: function (err) {
                callback(err);
            }
        });
    }

    function getAvatar() {
        async.retry(2, _getAvatar);
    }
    function _getAvatar(callback) {
        var $mainUserLink = $('#mainUserLink');
        $mainUserLink.html(window.ePlus.modules.userAvatar.renderLoadingAvatar('small'));
        $.ajax({
            type: "GET",
            cache: false,
            url: "api/me/user",
            contentType: "application/json",
            success: function (data) {
                const userAvatar = data.userAvatar;
                $mainUserLink.html(window.ePlus.modules.userAvatar.renderAvatar(userAvatar, 'small'));
                callback(null);
            },
            error: function (err) {
                callback(err);
            }
        });
    }

    return {
        initialize: initialize,
        getAvatar: getAvatar,
        getHeaderNoticeCount: getHeaderNoticeCount,
    }
})();
;
ePlus.modules.headerProfile = (function () {
    function initialize(userAdminArea) {
        getUserInfo();
        $('#log-out').click(function () {
            logOut();
        });
        $('#profile-user-profile').click(function () {
            openUserCenter(userAdminArea);
        });
        $('#profile-prefs-icon').click(function () {
            openPreferencesWindow('dashboard');
        });
        $('#profile-feedback').click(function () {
            openCustomerFeedback();
        })
        $('#back-to-support').click(function () {
            backToAdmin();
        })
        $('#profile-communication-settings').click(function (e) {
            openCommunicationSettings();
        });
    }

    function logOut() {
        closeModal();
        $("#popover_user").hide();
        $("#menuInterior").html("<div class='menuText'>" + getRes('logging_out') + "...</div>");
        $("#menuInterior").load("/GetTreelineControl.aspx?controlName=/uc/header/two_Header_LogOut.ascx");
    }

    function getUserInfo() {
        $('#header-profile-avatar').html(window.ePlus.modules.userAvatar.renderLoadingAvatar('large'));
        $.ajax({
            type: "GET",
            cache: false,
            url: "api/me/user",
            contentType: "application/json",
            success: function (data) {
                const user = data.user;
                const org = data.organization;
                const userAvatar = data.userAvatar;
                $('#header-profile-avatar').html(window.ePlus.modules.userAvatar.renderAvatar(userAvatar, 'large'));
                $('#profile-user-name').html(user.fullName);
                initializeProfileOrgName(org);
                initializeProfileCity(org);
                user && user.classification && $('#profile-classification').html(user.classification);
                var createdDate = user && user.createdDate && new Date(user.createdDate);
                createdDate && $('#profile-created-date').html(createdDate.toLocaleDateString());
            }
        });
    } 

    function initializeProfileOrgName(org) {
        var profileOrgName = org && org.displayName;
        $('#profile-org-name')
            .html(profileOrgName)
            .attr("title", profileOrgName);
    }

    function initializeProfileCity(org) {
        var profileCity = org && org.address && org.address.city && org.address.city;

        if (org && org.address && org.address.city && org.address.stateProvince) {
            if (profileCity) {
                profileCity += ', ';
            }
            profileCity += org.address.stateProvince;
        }

        $('#profile-city')
            .html(profileCity)
            .attr("title", profileCity);
    }

    return {
        initialize: initialize,
    }
})();
;
ePlus.modules.headerSearch = (function() {
  function initialize() {
    $("#search-option-toggle").webuiPopover({
      type: "async",
      cache: false,
      container: "#pageContent",
      backdrop: true,
      url:
        "/GetTreelineControl.aspx?controlName=/uc/search/GlobalTitleSearch.ascx",
      placement: "bottom",
      onShow: function() {
        window.isSearchOptionShown = true;
      }, 
      onHide: function() {
        window.isSearchOptionShown = false;
        hideFocus();
      } 
    });

    $("#headerSearchBox").hover(
      function() {
        $("#search-option-toggle").removeClass("hidden");
        showFocus();
      },
      function() {
        if (!$("#searchKeywords").is(":focus")) {
          hideFocus();
        }
      }
    );

    $("#searchKeywords")
      .focus(function() {
        showFocus();
      })
      .blur(function() {
        hideFocus();
      });
  }

    function showFocus() {
        $('#headerSearchBox')
            .css('border', '1px solid #d2d6d8')
            .css('padding', '4px');
    }

    function hideFocus() {
        if (!window.isSearchOptionShown) {
            $('#headerSearchBox')
                .css('border', '')
                .css('padding', '5px');
            $('#search-option-toggle')
                .addClass('hidden');
        }
    }

  return {
      initialize: initialize,
  };
})();
;
ePlus.modules.userAvatar = (function() {
  function renderLoadingAvatar(size) {
    var dHtml = '<div class="clear">';
    dHtml +=
      '<div class="circularLarger_NoShadow avatar-' +
      size +
      '" style="margin: 0 auto; background-color: #d2d6d8;">';
    dHtml += '<div class="center-box-parent">';
    dHtml += '<div class="center-box-child">';
    dHtml +=
      '<div class="accFont bold center" style="margin-top: 1px;">&nbsp;</div>';
    dHtml += "</div>";
    dHtml += "</div>";
    dHtml += "</div>";
    dHtml += "</div>";
    return dHtml;
  }

  function renderAvatar(userAvatar, size) {
    var dHtml = '<div class="clear">';
    if (userAvatar && userAvatar.hasImage) {
      dHtml += '<div class="center-box-parent avatar-' + size + '">';
      dHtml += '<div class="center-box-child">';
      dHtml +=
        '<div title="' +
        userAvatar.displayName +
        '"><img class="circularLarger_NoShadow avatar-' + size + '" src="' +
        userAvatar.uri +
        '"></div>';
      dHtml += "</div></div>";
    } else {
      dHtml +=
        '<div class="circularLarger_NoShadow avatar-' +
        size +
        '" style="margin: 0 auto; background-color: #d2d6d8;">';
      dHtml += '<div class="center-box-parent">';
      dHtml += '<div class="center-box-child">';
      dHtml +=
        '<div class="accFont bold center avatar-initials-' +
      size +
        '" style="margin-top: 1px;" title="' +
        userAvatar.displayName +
        '">' +
        userAvatar.initials +
        "</div>";
      dHtml += "</div>";
      dHtml += "</div>";
      dHtml += "</div>";
    }
    dHtml += "</div>";
    return dHtml;
  }

  return {
      renderAvatar: renderAvatar,
      renderLoadingAvatar: renderLoadingAvatar
  };
})();
;
window.ePlus.modules.orders = window.ePlus.modules.orders || {};
window.ePlus.modules.orders.lineItems = window.ePlus.modules.orders.lineItems || {};
window.ePlus.modules.orders.lineItems.repDiscounts = window.ePlus.modules.orders.lineItems.discounts || {};
    
window.ePlus.modules.orders.lineItems.repDiscounts.api = (function () {
    var getRepDiscountPercentUri = function (orderId, lineItemId) {
        return 'api/orders/' + orderId + '/lineitems/' + lineItemId + '/repDiscountPercent';
    };

    var deleteRepDiscountPercent = function (orderId, lineItemId) {
        return $.ajax({
            type: 'delete',
            url: getRepDiscountPercentUri(orderId, lineItemId)
        });
    };

    var updateRepDiscountPercent = function (orderId, lineItemId, discount) {
        return $.ajax({
            type: 'put',
            contentType: 'application/json',
            url: getRepDiscountPercentUri(orderId, lineItemId),
            data: JSON.stringify(discount)
        });
    };

    var saveRepDiscountPercent = function (orderId, lineItemId, discount) {
        return discount == null
            ? deleteRepDiscountPercent(orderId, lineItemId)
            : updateRepDiscountPercent(orderId, lineItemId, discount);
    }

    return {
        deleteRepDiscountPercent: deleteRepDiscountPercent,
        updateRepDiscountPercent: updateRepDiscountPercent,
        saveRepDiscountPercent: saveRepDiscountPercent
    };
})();;
window.ePlus.modules.orders = window.ePlus.modules.orders || {};
window.ePlus.modules.orders.lineItems = window.ePlus.modules.orders.lineItems || {};
window.ePlus.modules.orders.lineItems.repDiscounts = window.ePlus.modules.orders.lineItems.repDiscounts || {};
    
window.ePlus.modules.orders.lineItems.repDiscounts.ui = (function () {
    var api = window.ePlus.modules.orders.lineItems.repDiscounts.api;
    var keyCodes = window.ePlus.ui.keyCodes;

    var defaults = {
        culture: 'en',
        precision: 1,
        localizations: {
            error_discount_out_of_range: 'error_discount_out_of_range',
            error_saving_discount: 'error_saving_discount'
        }
    };
    var options = {};

    var initialize = function (settings) {
        options = $.extend({}, defaults, settings);

        $('.line-item-rep-discounts-action', '#pageContent').each(function () {
            var $self = $(this);
            var orderId = o.utils.getSelectedOrderId();
            var sku = $self.attr('data-sku');

            $self.webuiPopover({
                type: 'async',
                cache: false,
                url: '/orders/' + orderId + '/products/' + sku + '/lineItems/repDiscounts',
                container: '#pageContent',
                async: {
                    success: function () {
                        initializePopover(orderId, sku);
                    }
                }
            })
        });
    };

    var initializePopover = function (orderId, sku) {
        $('.line-item-rep-discount-input', '#pageContent').on({
            'keydown': function (e) {
                if (e.which === keyCodes.TAB
                    || e.which === keyCodes.DOWN_ARROW) {
                    e.preventDefault();
                    goToNextDiscountInput(this);
                }

                if (e.which === keyCodes.TAB && e.shiftKey
                    || e.which === keyCodes.UP_ARROW) {
                    e.preventDefault();
                    goToPreviousDiscountInput(this);
                }
            },
            'keyup': function (e) {
                if (e.which === keyCodes.ESC) {
                    e.stopPropagation(); // prevent popover from closing
                    revertDiscountValue(this);
                }

                if (e.which === keyCodes.ENTER
                    || e.which === keyCodes.ESC) {
                    $(this).blur();
                }
            },
            'blur': function () {
                if (!getIsDirty(this)) return;

                var discount = validateDiscount(this);

                if (discount.isValid) {
                    var lineItem = {
                        id: $(this).data('lineitemid'),
                        orderId: orderId,
                        sku: sku,
                        storeId: $(this).data('storeid'),
                        discount: discount.value
                    }

                    commitDiscountValue(this, lineItem);
                } else {
                    alert(options.localizations.error_discount_out_of_range);
                    revertDiscountValue(this);
                }
            },
            'change': function () {
                setIsDirty(this, true);
            }
        });
    };

    var goToNextDiscountInput = function (elem) {
        var $elem = $(elem);
        var $input = $elem.closest('tr').next().find('input');

        if ($input.length === 0) {
            $input = $elem.closest('tbody').find('input').first();
        }

        $input.focus().select();
    };

    var goToPreviousDiscountInput = function (elem) {
        var $elem = $(elem);
        var $input = $elem.closest('tr').prev().find('input');

        if ($input.length === 0) {
            $input = $elem.closest('tbody').find('input').last();
        }

        $input.focus().select();
    };

    var validateDiscount = function (elem) {
        var $elem = $(elem);
        var min = parseFloatOrNull($elem.attr('min'));
        var max = parseFloatOrNull($elem.attr('max'));
        var value = parseFloatOrNull(getDiscountValue(elem));
                
        return {
            value: value,
            isValid: value == null || (min == null || value >= min) && (max == null || value <= max)
        }
    };

    var parseFloatOrNull = function (value) {
        var value = parseFloat(parseFloat(value).toFixed(options.precision));
        return isNaN(value) ? null : value;
    };

    var commitDiscountValue = function (elem, lineItem) {
        setSavingIndicator(elem, true);

        api.saveRepDiscountPercent(lineItem.orderId, lineItem.id, lineItem.discount)
             .done(function () {
                updateDiscountValue(elem, lineItem.discount);
                updateListViewLineItemRepDiscount(lineItem);
                updateListViewLineItemAverageRepDiscount(lineItem.sku);
             })
            .fail(function () {
                alert(options.localizations.error_saving_discount);
                revertDiscountValue(elem);
            })
            .then(function () {
                setSavingIndicator(elem, false);
            });
    };

    var getDiscountValue = function (elem) {
        return $(elem).val();
    };

    var setDiscountValue = function (elem, value) {
        $(elem).val(value);
    };

    var getOriginalDiscountValue = function (elem) {
        return $(elem).data('original');
    };

    var setOriginalDiscountValue = function (elem, value) {
        return $(elem).data('original', value);
    };

    var getIsDirty = function (elem) {
        return $(elem).data('isdirty');
    };

    var setIsDirty = function (elem, value) {
        $(elem).data('isdirty', value);
    };

    var revertDiscountValue = function (elem) {
        var originalDiscount = getOriginalDiscountValue(elem);

        setDiscountValue(elem, originalDiscount);
        setIsDirty(elem, false);
    };

    var updateDiscountValue = function (elem, discount) {
        // Some browsers (like Chrome) don't support localization
        // of input[type=number] for locales that use the comma as
        // a decimal separator.  So here we're forcing the locale to
        // English when populating the discount input/original values.
        var formattedDiscount = format(discount, 'en'); 

        setDiscountValue(elem, formattedDiscount);
        setOriginalDiscountValue(elem, formattedDiscount);
        setIsDirty(elem, false);
    };

    var setSavingIndicator = function (elem, isSaving) {
        $(elem).toggleClass('bgdDarkGray', isSaving);
    };

    var updateListViewLineItemRepDiscount = function (lineItem) {
        var localizedDiscount = formatPercentage(lineItem.discount);

        $('#line-item-rep-discount-' + lineItem.sku + '-' + lineItem.storeId)
            .html(localizedDiscount);
    };

    var updateListViewLineItemAverageRepDiscount = function (sku) {
        var averageDiscount = calculateListViewLineItemAverageRepDiscount(sku);
        var localizedAverageDiscount = formatPercentage(averageDiscount);

        $('#line-item-average-rep-discount-' + sku)
            .html(localizedAverageDiscount);
    };

    var calculateListViewLineItemAverageRepDiscount = function (sku) {
        var count = 0;
        var sum = 0;

        $('.line-item-rep-discount-' + sku).each(function () {
            var discount = parseFloatOrNull($(this).html());

            if (discount == null) return;

            sum += discount;
            count++;
        });

        return count > 0 ? parseFloat((sum / count).toFixed(options.precision)) : null;
    }

    var format = function (value, culture) {
        return value == null ? '' : value.toLocaleString(culture || options.culture, {
            minimumFractionDigits: options.precision
        });
    };

    var formatPercentage = function (value) {
        return value == null ? '' : format(value) + '%';
    };

    return {
        initialize: initialize,
        updateListViewLineItemRepDiscount: updateListViewLineItemRepDiscount,
        updateListViewLineItemAverageRepDiscount: updateListViewLineItemAverageRepDiscount
    };
})();;
window.ePlus.modules.shares = window.ePlus.modules.shares || {};

window.ePlus.modules.shares.ui = (function () {
    var defaults = {
        width: 400,
        url: location.href
    };
    var options = {};

    var initializePopoverEventHandlers = function ($contentElement, url) {
        var $shareUrlInput = $('input.share-url', $contentElement);

        $shareUrlInput.val(url).select();

        $('button.share-copy-url', $contentElement).on('click', function () {
            $shareUrlInput.select();
            document.execCommand('copy');
            return false;
        });
    };

    var initializePopover = function (elem) {
        $(elem).webuiPopover({
            type: 'async',
            cache: false,
            url: '/shares',
            width: options.width,
            async: {
                success: function (popover) {
                    var url = options.url && typeof options.url === 'function'
                        ? options.url()
                        : options.url;
                    initializePopoverEventHandlers(popover.$contentElement, url);
                }
            }
        });
    };

    var initialize = function (settings) {
        options = $.extend({}, defaults, settings);

        $(options.selector).each(function () {
            initializePopover(this);
        });
    };

    return {
        initialize: initialize
    };
})();;
