/* BROWSER DETECTION */
jQuery.extend(jQuery.browser,
	{Safari: navigator.userAgent.toLowerCase().match(/Safari/i)},
	{SafariMobile : navigator.userAgent.toLowerCase().match(/iP(hone|ad|od)/i)}
	);

$(function () {
	if ($.browser.SafariMobile) {
		$('html').addClass('iOS');
	} else {
		$('html').addClass('no-iOS');
	}
	if ($.browser.Safari) {
		$('html').addClass('Safari');
	} else {
		$('html').addClass('no-Safari');
	}
});

/* UTILITY FUNCTIONS */
/**
 * Will toggle selected class on an element (by id) within a context, and deselect the other items
 * @param {string} id Which item to highlight
 * @param {string} location Context for the item
 **/
function selectItemByIndex(id, location) {
	$(location).each(function (i, item) {
		$(item).toggleClass('selected', i == id);
	});
}
/**
 * Will toggle selected class on an element within a context, and deselect the other items
 * @param {object} $trigger jQuery object to highlight
 * @param {string} location Context for the item
 **/
function selectItemByTrigger($trigger, location) {
	$(location).each(function (i, item) {
		$(item).removeClass('selected');
	});
	$trigger.addClass('selected');
}
/**
 * Parses the url and returns an object that contains the
 * url params as key-value pairs
 * @returns {object} key value pairs of url params. 
 **/
function getUrlVars() {
	var vars = {},
		tmp = [],
		hash,
		hashes = (window.location.href.indexOf('#') > 0) ? (document.location.href.split('#')[0]).slice(window.location.href.indexOf('?') + 1).split('&') : window.location.href.slice(window.location.href.indexOf('?') + 1).split('&'),
		i;
	for (i = 0; i < hashes.length; i++) {
		hash = hashes[i].split('=');
		if (typeof (vars[hash[0]]) != 'undefined') {
			tmp = [];
			tmp.push(vars[hash[0]]);
			tmp.push(hash[1]);
			vars[hash[0]] = tmp;
		} else {
			vars[hash[0]] = hash[1];
		}
	}
	return vars;
}

/**
 * Returns a de-duped version of your array
 * @returns {array} array with duplicate values removed
 **/
Array.prototype.dedup = function () {
	var newArray = [],
		seen = {},
		i;
	for (i = 0; i < this.length; i++) {
		if (seen[this[i]]) {
			continue;
		}
		newArray.push(this[i]);
		seen[this[i]] = 1;
	}
	return newArray;
};
/**
 * Checks if an array contains a value.
 * @param {string} obj the value to test for
 * @returns {boolean}
 **/
Array.prototype.contains = function (obj) {
	var i = this.length;
	while (i--) {
		if (this[i] === obj) {
			return true;
		}
	}
	return false;
};
/**
 * array sort function for product dropdowns
 * @param {string} propertyName property to sort by
 * @returns {number} returns -1, 0, or 1 
 */
function prioritySort(propertyName) {
	return function (a, b) {
		var aPriority,
			bPriority,
			aName,
			bName;
		if ((a.attributes[propertyName] !== undefined) && (b.attributes[propertyName] !== undefined)) {
			aPriority = a.attributes[propertyName].priority;
			bPriority = b.attributes[propertyName].priority;
			if (aPriority == bPriority) {
				aName = a.attributes[propertyName].displayName.toLowerCase();
				bName = b.attributes[propertyName].displayName.toLowerCase();
				if (aName < bName) {
					return -1;
				} else if (aName > bName) {
					return 1;
				}
			} else {
				return aPriority - bPriority;
			}
		}
		return 0; // no sorting
	};
}
/**
 * array sort function for gift cert dropdown
 * @param {string} propertyName property to sort by
 * @returns {number} returns -1, 0, or 1 
 */
function displayNameSort(propertyName) {
	return function (a, b) {
		if ((a.attributes[propertyName] !== undefined) && (b.attributes[propertyName] !== undefined)) {
			var aName = a.attributes[propertyName].displayName.replace(/[^0-9]/g, ''),
				bName = b.attributes[propertyName].displayName.replace(/[^0-9]/g, '');
			return aName - bName;
		}
		return 0;// no sorting
	};
}
/**
 * Pass me an object and I'll tell you the size.
 * @returns {number} size of object
 **/
Object.size = function (obj) {
	var size = 0, key;
	for (key in obj) {
		if (obj.hasOwnProperty(key)) {
			size++;
		}
	}
	return size;
};
/**
 * LEGACY function that scrolls the page to an anchor.
 **/
function jumpToAnchor(anchorId) {
	var $anchor = $('#' + anchorId),
		new_position;
	if ($anchor.length != 0) {
		new_position = $anchor.offset();
		window.scrollTo(new_position.left, new_position.top);
	}
}
/**
 * Boundry test to see if mouse event is within a bounding box. 
 * @returns {boolean} true if mouse is in the bounding box, false otherwise.
 * @param {number} x horizontal mouse position
 * @param {number} y vertical mouse position
 * @param {object} bBox object containing bounding box  top, right, bottom, and left coordinates
 **/
function mouseTest(x, y, bBox) {
	return (x < bBox.l) || (x >= bBox.r) || (y < bBox.t) || (y >= bBox.b);
}

/**
 * Expanding Banner extension. Shows a expanding promo banner based on an engagement cookie and page load count.
 * Requires the cookie jquery extension.
 * @param {object} options
 * @returns {object} jquery object
 **/
(function ($) {
	$.fn.expandingBanner = function (options) {
		options = $.extend({
			promocookie: "promobanner",
			delay: 1,
			persist: 20,
			expire: 0,
			pageloads: 0,
			emailcheck: false,
			highlight: false,
			title: ""
		}, options);
		this.each(function () {
			var banner = $(this),
				promoDisplayBlock = 'null',
				t,
				mouseOutDelay = 10000,
				cmTitle = (options.title != "") ? "expanding-banner " + options.title : "expanding-banner";
			if (options.emailcheck) {
				promoDisplayBlock = ($.cookie('emailEntered'));
			}
			if (($.cookie(options.promocookie) != 'viewed') && (promoDisplayBlock != 'true') && (parseInt($.cookie("engagement")) >= options.pageloads)) {
				// Show Banner
				window.setTimeout(function () {
					if (options.highlight == 'highlight') {
						banner.effect("highlight", {color: '#5F5853'}, 2000);
					} else {
						banner.slideDown('slow');
					}
					//cm tracking
					cmCreatePageviewTag(cmTitle, "", "expanding-banner");
					// Set cookie
					$.cookie(options.promocookie, 'viewed', {expires: options.expire / 24, path: '/'});
				}, options.delay * 1000);
				banner.find('.closeBanner').click(function () {
					banner.slideUp('fast');
				});
				// Delay before hiding banner
				t = window.setTimeout(function () {
					banner.slideUp();
				}, options.persist * 1000);
				banner.find('input').focus(function () {
					mouseOutDelay = 500000;
				}).blur(function () {
					mouseOutDelay = 5000;
				});
				banner.hover(function () {
					clearTimeout(t);
				}, function () {
					t = window.setTimeout(function () {
						banner.slideUp();
					}, mouseOutDelay);
				});
			}
		});
		return this;
	};
})(jQuery);

/**
 * Expanding List extension. Creates a more/less label at the bottom of a list that shows and hides the rest of the list.
 * @param {object} options - you can set the collapsed size of the list, toggle a count (show 2 more), and add a label (show 2 more colors)
 * @returns {object} jquery object
 **/
(function ($) {
	$.fn.collapsableList = function (options) {
		options = $.extend({
			size: 10,
			label: false,
			showCount: true
		}, options);
		this.each(function () {
			var $list = $(this),
				size = options.size,
				hideVal = size - 1,
				label = options.label || $list.attr('data-label'),
				showLabel = options.showLabel || '',
				hideLabel = options.hideLabel || '',
				listSize = $list.children('li').length,
				sizeLabel = (options.showCount) ? listSize - size : '';
			
			if (listSize > size) {
				if (showLabel == '') {
					if (label != undefined && label != '') {
						if (listSize - size > 1) {
							showLabel = '&rsaquo; ' + sizeLabel + ' more ' + label + 's';
						} else {
							showLabel = '&rsaquo; ' + sizeLabel + ' more ' + label;
						}
					} else {
						showLabel = '&rsaquo; ' + sizeLabel + ' more';
					}
				}
				if (hideLabel == '') {
					if (label != undefined && label != '') {
						hideLabel = '&lsaquo; fewer ' + label + 's';
					} else {
						hideLabel = '&lsaquo; less';
					}
				}
				/* Hide extra dimensions and add link to show more */
				$list.children('li:gt(' + hideVal + ')').hide();
				$list.append('<li class="more-items"><a href="#"><strong>' + showLabel + '</strong>...</a></li>')
					/* show more on click, remove link and add remove link */
					.delegate('.more-items', 'click', function () {
						$list.append('<li class="fewer-items"><a href="#"><strong>' + hideLabel + '</strong></a></li>')
							.children('li:gt(' + hideVal + ')').show();
						$('.more-items', $list).remove();
						return false;
					})
					/*
					 * back to default: hide extra dimensions on click, remove
					 * link, add more link
					 */
					.delegate('.fewer-items', 'click', function () {
						$list.children('li:gt(' + hideVal + ')').hide();
						$list.append('<li class="more-items"><a href="#"><strong>' + showLabel + '</strong>...</a></li>');
						$('.fewer-items', $list).remove();
						return false;
					});
			}
		});
		return this;
	};
})(jQuery);

/**
 *  Lifestyle Promo Display. This displays the targets on a lifestyle image.
 * @param {object} options. nothing in this yet.
 * @returns {object} jquery object
 **/
(function ($) {
	$.fn.lifestyleDisplay = function (options) {
		options = $.extend({
			visible: true // initial visibility state
		}, options);
		this.each(function () {
			var $container = $(this),
				containerWidth = $container.width(),
				containerHeight = $container.height();
			$('.lifestyle-info', $container).each(function () {
				var $this = $(this),
					divWidth = $this.width(),
					divHeight = $this.height(),
					divPos = $this.parent().position(),
					divTop = 0 - (divHeight / 2),
					divLeft = 0 - (divWidth / 2),
					positionData = {},
					flyoutDirection = 'top';
				/* get the default flyout position for the target */
				if ($this.hasClass('flyout-top')) {
					flyoutDirection = 'top';
				} else if ($this.hasClass('flyout-bottom')) {
					flyoutDirection = 'bottom';
				} else if ($this.hasClass('flyout-right')) {
					flyoutDirection = 'right';
				} else if ($this.hasClass('flyout-left')) {
					flyoutDirection = 'left';
				}
				/* position the elements */
				if (flyoutDirection == 'top' || flyoutDirection == 'bottom') {
					positionData.left = divLeft;
				} else {
					positionData.top = divTop;
				}
				/* edge avoidance. changes the flyout direction */
				if ((divPos.top - divHeight / 2) < 0 || (flyoutDirection == 'top' && (divPos.top - divHeight) < 0)) {
					if ((divPos.left - divWidth / 2) < 0 || (flyoutDirection == 'left' && (divPos.left - divWidth) < 0)) {
						positionData = {top: 0, bottom: 'auto', left: 0, right: 'auto'};	// top left corner
					} else if ((containerWidth - divPos.left) < divWidth / 2 || (flyoutDirection == 'right' && (divPos.left - divWidth) < 0)) {
						positionData = {top: 0, bottom: 'auto', left: 'auto', right : 0};	//top right corner
					} else {
						positionData = {top: 0, bottom: 'auto', left: divLeft, right: 'auto'};	// top
					}
				} else if ((containerWidth - divPos.left) < divWidth / 2 || (flyoutDirection == 'right' && (containerWidth - divPos.left) < divWidth)) {
					if ((containerHeight - divPos.top) < divHeight / 2 || (flyoutDirection == 'bottom' && (containerHeight - divPos.top) < divHeight)) {
						positionData = {left: 'auto', right: 0, bottom: 0, top: 'auto'};	// bottom right
					} else {
						positionData = {left: 'auto', right: 0, bottom: 'auto', top: divTop};	// right
					}
				} else if ((containerHeight - divPos.top) < divHeight / 2 || (flyoutDirection == 'bottom' && (containerHeight - divPos.top) < divHeight)) {
					if ((divPos.left - divWidth / 2) < 0) {
						positionData = {top: 'auto', bottom: 0, left: 0, right: 'auto'};	// bottom left
					} else {
						positionData = {top: 'auto', bottom: 0, left: divLeft, right: 'auto'};	// bottom
					}
				} else if ((divPos.left - divWidth / 2) < 0 || (flyoutDirection == 'left' && (divPos.left - divWidth) < 0)) {
					positionData = {left: 0, right: 'auto', top: divTop, bottom: 'auto'};	// left
				}
				$this.css(positionData);
			});

			$container.click(function (e) {
				var $target = $(e.target),
					prodId = '',
					skuId = '';
				/* add the click event to open quickview */
				if ($target.hasClass('quickview-link')) {
					prodId = $target.attr('data-productId');
					skuId = $target.attr('data-skuId');
					quickView.launch({'productId': prodId, 'skuId': skuId});
				}
				/* add the click event to show the info overlay */
				if ($target.hasClass('lifestyle-icon')) {
					$target.parent().toggleClass('inactive').toggleClass('active');
				}
				return false;
			});

			/* bring in the targets */
			if (options.visible == true) {
				$('.lifestyle-icon', $container).fadeIn();
				$('.lifestyle-sku-list', $container).fadeIn();
			}
		});
		return this;
	};
})(jQuery);

function initializePromoDisplay(image) {
	$(image).parent().lifestyleDisplay();
}

/**
 * AJAX Call for rib-splitter promo banner.
 * TODO: Review this. I think we may call it too often.
 **/
function getPromoBanner() {
	$.ajax({
		url: '/sitewide/includes/header/expanding-banner-controller.jsp?categoryId=' + categoryId + '&section=' + section + '&subsection=' + subsection,
		dataType: 'html',
		cache: false,
		success: function (data) {
			if (data.length > 0) {
				$(document).ready(function () {
					$('#promo-container').html(data);
				});
			}
		}
	});
}

/**
 * Used to restrict character count on input boxes.
 * @param input {object} Dom input element
 * @param maxLength {number} maximum number of characters
 * @param alertText {string} error to alert when over max
 **/
function characterCount(input, maxLength, alertText) {
	// get current number of characters
	var goodValue;
	$(input).each(function () {
		// update characters
		$(this).focus(function () {
			var length = $(this).val().length;
			$(this).next('span').remove();
			$(this).after('<span>' + (maxLength - length) + ' ' + alertText + '</span>');
		});
		$(this).blur(function () {
			$(this).next('span').remove();
		});
		// bind on key up event
		$(this).keyup(function () {
			// get new length of characters
			var new_length = $(this).val().length;
			if (new_length < maxLength) {
				$(this).next('span').removeClass('error');
			} else if (new_length == maxLength) {
				$(this).next('span').addClass('error');
				goodValue = $(this).val();
			} else {
				$(this).val(goodValue);
			}
			new_length = $(this).val().length;
			// print
			$(this).next('span').html((maxLength - new_length) + ' ' + alertText);
		});
	});
}

/**
 * TODO: rename this function to make it more general
 * opens an element from an anchor link 
 */
function openFaq(id) {
	$(id).parent().next('dd').show();
}

// STATE SELECT OR TEXT INPUT SWITCH ON COUNTRY SELECT
function switchBillingState(country, stateSelect, stateInput) {
	var billingCountry = $(country).children('option:selected').attr('value').toUpperCase(),
		$stateSelectSet = $(stateSelect).parent(),
		$stateInputSet = $(stateInput).parent();
	if (billingCountry && billingCountry.toUpperCase() != "US" && billingCountry.toUpperCase() != "CA") {
		$stateSelectSet.removeClass('hidden').addClass('hidden').children('select').attr('disabled', 'disabled');
		$stateInputSet.removeClass('hidden');
	} else {
		$stateInputSet.removeClass('hidden').addClass('hidden').children('input').val('');
		$stateSelectSet.removeClass('hidden').children('select').removeAttr('disabled');
	}
}

/**
 * Mini Cart
 * @constructor
 * @param {object} config
 */
var miniCart = {
	miniCartWindow: '#mini-cart',
	miniCartTrigger: '#util-cart',
	miniCartStatus : false,
	/* miniCart.mcLoad()
	 * Function to show the mini cart 
	 * If the mini cart ajax request has already been made, just show the mini cart.
	 * Otherwise, make the ajax request and show the loading miniCart.
	 */
	mcLoad : function () {
		var $miniCartTrigger = $(this.miniCartTrigger),
			$miniCartWindow = $(this.miniCartWindow),
			triggerPos = $miniCartTrigger.offset(),
			triggerHeight = $miniCartTrigger.height();
		if (this.miniCartStatus === true) {
			$miniCartTrigger.addClass('selected');
			$miniCartWindow.css({top: triggerPos.top + triggerHeight}).show();
		} else {
			$miniCartWindow.bgiframe();
			$miniCartTrigger.addClass('selected');
			$miniCartWindow.css({top: triggerPos.top + triggerHeight}).addClass('loading').show();
			$.ajax({
				url: '/modal/mini-cart.jsp',
				type: 'GET',
				dataType: 'html',
				cache: false,
				success: function (data) {
					miniCart.mcShow(data);
				},
				error: function (XMLHttpRequest, textStatus, errorThrown) {
					this.miniCartStatus = false;
					// no response
				}
			});
			this.miniCartStatus = true;
		}
		return false;
	},
	/* miniCart.mcShow()
	 * function to show miniCart
	 */
	mcShow : function (data) {
		$(this.miniCartWindow).removeClass('loading').html(data);
	},
	/* miniCart.mcHide()
	 * function to hide mini cart. Does not erase mini cart contents.
	 */
	mcHide : function () {
		$(this.miniCartTrigger).removeClass('selected');
		$(this.miniCartWindow).hide();
	},
	/* miniCart.mcUpdate({int})
	 * function to update cart count if new item is added.
	 */
	mcUpdate : function (count) {
		$(this.miniCartTrigger + ' > a ').html('Cart (' + count + ')');
		$(this.miniCartWindow).empty();
		this.miniCartStatus = false;
	},
	/* miniCart.mcClear()
	 * function to update cart count if new item is added.
	 */
	mcClear : function () {
		$(this.miniCartWindow).empty();
		this.miniCartStatus = false;
	}
};

/**
 * Asynchronously loads images with optional callbacks.
 * @constructor
 * @param {object} config Contains callbacks for onStart, onComplete, and onLoaded.
 **/
var ImageQueueLoader = function (config) {
	this.onComplete = function () {};
	this.onLoaded = function () {};
	this.onStart = function () {};
	this.current = null;
	this.images = []; // Array of loaded image objects
	this.completed = []; // Array of loaded image urls
	this.queue = []; // Array of URIs waiting to be processed

	//optional callbacks
	if (config !== undefined) {
		if (config.onStart !== undefined && typeof (config.onStart) === "function") {
			this.onStart = config.onStart;
		}
		if (config.onComplete !== undefined && typeof (config.onComplete) === "function") {
			this.onComplete = config.onComplete;
		}
		if (config.onLoaded !== undefined && typeof (config.onLoaded) === "function") {
			this.onLoaded = config.onLoaded;
		}
	}
};

ImageQueueLoader.prototype = {
	/* 
	 * queueImages function takes an array or string, 
	 * places new items at top of loading queue
	 */
	queueImages: function () {
		var _self = this,
			i;
		for (i = 0; i < arguments.length; i++) {
			if (arguments[i].constructor === Array) {
				_self.queue = arguments[i].concat(_self.queue);
			} else if (typeof arguments[i] === 'string') {
				_self.queue.unshift(arguments[i]);
			}
		}
	},
	/* 
	 * processQueue loops through the loading queue and loads each image
	 */
	processQueue: function () {
		while (this.queue.length > 0) {
			this.loadImage(this.queue.shift());
		}
	},
	/* 
	 * loadImage first checks to make sure this image has not already loaded,
	 * if it hasn't it loads the image. After user agent has the image, set 
	 * the current, add the image to the stack and fire the onloaded callback
	 */
	loadImage: function (imageSrc) {
		var _self = this,
			imgObj;
		if ($.inArray(imageSrc, _self.completed) == -1) {
			(_self.onStart)(imageSrc);
			imgObj = new Image();
			imgObj.onload = function () {
				_self.current = imgObj;
				if ($.inArray(imageSrc, _self.completed) == -1) {
					_self.images.push(imgObj);
					_self.completed.push(imageSrc);
				}
				(_self.onLoaded)(imageSrc);
			};
			imgObj.src = imageSrc;
		}
	}
};


/**
 * Room Gallery Slideshow
 * @constructor
 * @param {object} config
 * requires address plugin
 */
var GalleryViewer = function (config) {

	this.container = (config !== undefined && config.container !== undefined) ? config.container : '#gallery-viewer';
	this.galleryWidth = (config !== undefined && config.galleryWidth !== undefined) ? config.galleryWidth : 1000;
	this.thumbnailCount = (config !== undefined && config.thumbnailCount !== undefined) ? config.thumbnailCount : 5;
	this.slideIdArray = [];
	this.albumData = {};
	this.slideData = {};
	this.$slideContainer = $('#gallery-slides', this.container);
	this.$slideSlider = $('.items', this.$slideContainer);
	this.$albumContainer = $('#gallery-navigation', this.container);
	this.$thumbContainer = $('#slide-navigation', this.container);
	this.$slides = $('.slide', this.$slideContainer);
	this.$thumbnails = $('.thumbnail', this.$thumbContainer);
	this.$slideNavPrev = $('.prev a', this.$slideContainer);
	this.$slideNavNext = $('.next a', this.$slideContainer);
	this.$thumbNavPrev = $('.prev', this.$thumbContainer);
	this.$thumbNavNext = $('.next', this.$thumbContainer);
	this.thumbnailWidth = $('#slide-navigation-scrollable', this.$thumbContainer).width();
	this.slideImages = [];
	this.thumbImages = [];
	this.slideCount = this.$slides.length;
	this.slideIndex = 0;
	this.currentAlbumId = "";
	this.slideLoadStatus = {};
	this.thumbIndex = 0;
	this.transitionStyle = "";
	this.busy = false;
	this.slideImageQueue = new ImageQueueLoader({onStart: this.showLoader, onLoaded: this.insertSlides});
	this.thumbImageQueue = new ImageQueueLoader({onStart: this.showLoader, onLoaded: this.insertThumbnails});
	this.imageLoadComplete = false;
	this.thumbLoadComplete = false;

	//initialize the GalleryViewer
	this.init();

};

GalleryViewer.prototype = {
	/*
	 * Initialization function.
	 * Pulls data from the slides. Sets the height of the slideshow based off the first image to load.
	 * Clones the first and last slide for a continuous carosel. Adds the navigation click events and 
	 * the address change listener.
	 */
	init : function () {
		var _self = this,
			i = 0,
			max = this.slideCount,
			getparams = getUrlVars(),
			initialSlideId,
			$slide,
			currentId = $('.current', this.$slideContainer).attr('data-slideid'),
			loadQueue;

		for (i = 0; i < max; i++) {
			$slide = $(this.$slides.eq(i));
			this.slideIdArray.push($slide.attr('data-slideid'));
		}

		$('.lifestyle-image').each(function () {
			_self.slideImages.push($(this).attr('data-src'));
		});
		$('.thumbnail-image').each(function () {
			_self.thumbImages.push($(this).attr('data-src'));
		});

		this.slideIndex = $.inArray(currentId, this.slideIdArray);
		this.thumbIndex = this.slideIndex;

		this.setThumbnails();
		this.slideLoadStatus[currentId] = true;
		_self.highlightActiveSlide();
		_self.highlightActiveAlbum();
		$('.lifestyle-container', '#slide-' + currentId).lifestyleDisplay();

		//set slideshow height
		$('.selected img', this.$thumbContainer).load(function () {
			var $this = $(this),
				imgHeight = $this.height(),
				imgWidth = $this.width();
			_self.thumbHeight = $this.parents('.thumbnail').height();
			$('.scrollable-wrapper', _self.$thumbContainer).height(_self.thumbHeight);
			$('.thumbnail', _self.$thumbContainer).height(_self.thumbHeight);
			$('.thumbnail a').css({'height': imgHeight, 'width': imgWidth});
		}).each(function () {
			if (this.complete || (jQuery.browser.msie && parseInt(jQuery.browser.version) == 6)) {
				$(this).trigger("load");
			}
		});
		$('.current img', this.$slideContainer).load(function () {
			_self.slideHeight = $(this).height();
			$(_self.$slideContainer).height(_self.slideHeight);
			$('.slide', _self.$slideContainer).height(_self.slideHeight);
			$('.slide > div', _self.$slideContainer).height(_self.slideHeight);
		}).each(function () {
			if (this.complete || (jQuery.browser.msie && parseInt(jQuery.browser.version) == 6)) {
				$(this).trigger("load");
			}
		});

		//clone slides for wrap
		this.$slides.eq(0).clone().attr('id', '').addClass('cloned').appendTo(this.$slideSlider);
		this.$slides.eq(this.slideCount - 1).clone().attr('id', '').addClass('cloned').prependTo(this.$slideSlider);
		_self.$slideSlider.css({left: -_self.slideIndex * _self.galleryWidth - _self.galleryWidth});

		//listen
		$.address.change(function (event) {
			var slideId = event.value;
			slideId = slideId.replace(/\//, '');
			_self.navigate(slideId);
		});

		_self.$slideNavNext.click(function () {
			if (_self.busy == false) {
				var nextSlideIndex = (_self.slideIndex + 1 < _self.slideCount) ? _self.slideIndex + 1 : 0,
					nextSlideId = _self.slideIdArray[nextSlideIndex];
				$.address.value(nextSlideId);
			}
			return false;
		});

		_self.$slideNavPrev.click(function () {
			if (_self.busy == false) {
				var prevSlideIndex = (_self.slideIndex - 1 >= 0) ? _self.slideIndex - 1 : _self.slideCount - 1,
					prevSlideId = _self.slideIdArray[prevSlideIndex];
				$.address.value(prevSlideId);
			}
			return false;
		});

		_self.$thumbNavPrev.click(function () {
			var prevSlideIndex = Math.abs((_self.thumbIndex + _self.slideCount - 1) % _self.slideCount),
				prevSlideId = _self.slideIdArray[prevSlideIndex];
			$.address.value(prevSlideId);
			return false;
		});

		_self.$thumbNavNext.click(function () {
			var nextSlideIndex = (_self.thumbIndex + _self.thumbnailCount) % _self.slideCount,
				nextSlideId = _self.slideIdArray[nextSlideIndex];
			$.address.value(nextSlideId);
			return false;
		});

		_self.$thumbContainer.click(function (e) {
			var $target,
				slideId;
			$target = $(e.target);
			if ($target.hasClass('thumbnail-image')) {
				slideId = $target.attr('data-slideId');
				$.address.value(slideId);
			}
			return false;
		});

		_self.$albumContainer.click(function (e) {
			var $target,
				slideId;
			if (_self.busy == false) {
				$target = $(e.target);
				if ($target.hasClass('gallery-link')) {
					slideId = _self.getAlbumSlideId($target.attr('data-albumId'));
					$.address.value(slideId);
				}
			}
			return false;
		});

		//if no hash, do navigation based on id
		if (window.location.href.indexOf('#') < 0) {
			if (getparams.slideId != undefined && getparams.slideId != "") {
				$.address.value(getparams.slideId);
			} else if (getparams.id != undefined && getparams.id != "") {
				initialSlideId = _self.getAlbumSlideId(getparams.id);
				$.address.value(initialSlideId);
			}
		}
	},
	/*
	 * adds the highlight style to the current slide thumbnail.
	 */
	highlightActiveSlide : function (currentSlideIndex) {
		var slideIndex = (currentSlideIndex != undefined && currentSlideIndex > -1) ? currentSlideIndex : this.slideIndex;
		$('div.thumbnail a').removeClass('selected');
		this.$thumbnails.eq(slideIndex).find('a').addClass('selected');
	},
	/*
	 * Checks which album the current slide belongs to and adds the highlight style to the current album title.
	 */
	highlightActiveAlbum : function () {
		var albumId = this.$slides.eq(this.slideIndex).attr('data-albumid'),
			$activeNav = {};
		if (this.currentAlbumId != albumId) {
			$activeNav = $('.scrollable-title a[data-albumid="' + albumId + '"]');
			selectItemByTrigger($activeNav, '.scrollable-title a');
			this.currentAlbumId = albumId;
		}
	},
	/*
	 * Set up the visible thumbnails and the next and previous sets. Adds the thumbail images to the thumbnail queue loader
	 * Used when you jump to a point in the slideshow or after you use the next and previous navigation in the thumbnail bar.
	 */
	setThumbnails : function (currentSlideIndex) {
		var _self = this,
			slideIndex = (currentSlideIndex != undefined && currentSlideIndex > -1) ? currentSlideIndex : this.slideIndex,
			x,
			currPanelSlideIndex = slideIndex,
			prevPanelSlideIndex = Math.abs((slideIndex + this.slideCount - _self.thumbnailCount) % this.slideCount),
			nextPanelSlideIndex = (slideIndex + _self.thumbnailCount) % this.slideCount,
			thumbLoadQueue = [];
		this.$thumbnails.attr('style', '').removeClass('on-stage');
		for (x = 0; x < this.thumbnailCount; x++) {
			this.$thumbnails.eq((currPanelSlideIndex + x) % this.slideCount).css('left', 190 * x).addClass('on-stage');
			this.$thumbnails.eq((prevPanelSlideIndex + x) % this.slideCount).css('left', (190 * x) - (190 * this.thumbnailCount)).addClass('on-stage');
			this.$thumbnails.eq((nextPanelSlideIndex + x) % this.slideCount).css('left', (190 * x) + (190 * this.thumbnailCount)).addClass('on-stage');
		}
		thumbLoadQueue = this.getThumbQueue(slideIndex);
		if (this.thumbLoadComplete == false) {
			this.thumbImageQueue.queueImages(thumbLoadQueue);
			this.thumbImageQueue.processQueue();
		}
	},
	/*
	 * Navigte to another slide. Determines the distance between your current slide and the 
	 * next slide and performs either a sliding transition or a jump. Updates the Image loading queue.
	 */
	navigate : function (slideId) {
		if (slideId == '' || slideId == undefined) {
			slideId = this.slideIdArray[0];
		}
		var _self = this,
			currentSlideIndex = _self.slideIndex,
			jumpSlideIndex = $.inArray(slideId, this.slideIdArray),
			distance,
			size,
			count,
			loadQueue;

		if (jumpSlideIndex == -1) {
			jumpSlideIndex = 0;
		}

		distance = _self.getDistance(currentSlideIndex, jumpSlideIndex);
		if (this.imageLoadComplete == false) {
			loadQueue = this.getLoadQueue(jumpSlideIndex);
			this.slideImageQueue.queueImages(loadQueue);
			this.slideImageQueue.processQueue();
		}

		if (jumpSlideIndex != currentSlideIndex) { // no drag
			// clean out previous animations
			_self.$slideSlider.clearQueue();

			if (distance == 1) {
				//navigate next
				_self.busy = true;
				_self.slideIndex = jumpSlideIndex;
				_self.navigateThumbStrip(_self.slideIndex);
				_self.highlightActiveAlbum();
				_self.$slideSlider.animate({left: '-=' + _self.galleryWidth}, '', function () {
					_self.loadRoom(slideId);
					if (_self.slideIndex == 0) {
						_self.$slideSlider.css({left: -_self.galleryWidth});
					}
					_self.busy = false;
				});
			} else if (distance == -1) {
				//navigate previous
				_self.busy = true;
				_self.slideIndex = jumpSlideIndex;
				_self.navigateThumbStrip(_self.slideIndex);
				_self.highlightActiveAlbum();
				_self.$slideSlider.animate({left: '+=' + _self.galleryWidth}, '', function () {
					_self.loadRoom(slideId);
					if (_self.slideIndex == _self.slideCount - 1) {
						_self.$slideSlider.css({left: -_self.slideIndex * _self.galleryWidth - _self.galleryWidth});
					}
					_self.busy = false;
				});
			} else if (distance != 0) {
				// jump forward
				_self.busy = true;
				_self.slideIndex = jumpSlideIndex;
				_self.navigateThumbStrip(_self.slideIndex);
				_self.highlightActiveAlbum();
				_self.$slideSlider.css({left: -_self.slideIndex * _self.galleryWidth - _self.galleryWidth});
				_self.loadRoom(slideId);
				_self.busy = false;
			} else {
				_self.busy = false;
			}
		}
	},
	/*
	 * Gets the distance (count) between slides. Supports the wrapping from the last slide to the first slide.
	 */
	getDistance : function (origin, destination) {
		var leftDistance,
			rightDistance;
		if (origin > destination) {
			leftDistance = origin - destination;
			rightDistance = this.slideCount - origin + destination;
		} else {
			leftDistance = origin + (this.slideCount - destination);
			rightDistance = destination - origin;
		}
		if (leftDistance < rightDistance) {
			return (-leftDistance);
		} else {
			return rightDistance;
		}
	},
	/*
	 * Controls the animation of the thumbnail strip. Determines if the stip can stay where it it, 
	 * needs to slide or has to jump. Highlights the active slide.
	 */
	navigateThumbStrip : function (currentSlideIndex) {
		var _self = this,
			slideIndex = (currentSlideIndex != undefined && currentSlideIndex > -1) ? currentSlideIndex : this.slideIndex,
			distance = _self.getDistance(this.thumbIndex, slideIndex, this.slideCount),
			$activeSlides = $('.on-stage', _self.$thumbContainer),
			size = $activeSlides.length - 1,
			count = 0;
		// reset any previous animations
		$activeSlides.clearQueue();
		_self.setThumbnails(_self.thumbIndex);

		if (distance > 0 && distance < _self.thumbnailCount) {
			//highlight
		} else if (distance >= _self.thumbnailCount && distance < (_self.thumbnailCount * 2)) {
			// slide forward
			$activeSlides.animate({left: '-=' + _self.thumbnailWidth}, '', function () {
				if (++count == size) {
					_self.thumbIndex = (_self.thumbIndex + 5) % _self.slideCount;
					_self.setThumbnails(_self.thumbIndex);
				}
			});
		} else if (distance < 0 && distance > (-_self.thumbnailCount)) {
			// slide back
			$activeSlides.animate({left: '+=' + _self.thumbnailWidth}, '', function () {
				if (++count == size) {
					_self.thumbIndex = Math.abs((_self.thumbIndex + _self.slideCount - 5) % _self.slideCount);
					_self.setThumbnails(_self.thumbIndex);
				}
			});
		} else if (distance != 0) {
			// jump time
			_self.thumbIndex = _self.slideIndex;
			_self.setThumbnails(_self.thumbIndex);
		}
		_self.highlightActiveSlide();
	},
	/*
	 * gets the next slide's index. Supports the wrapping from the last slide to the first.
	 */
	getNextSlideIndex : function (index) {
		return (index + 1) % this.slideCount;
	},
	/*
	 * gets the previous slide's index. Supports the wrapping from the first slide to the last.
	 */
	getPrevSlideIndex : function (index) {
		return Math.abs((index + this.slideCount - 1) % this.slideCount);
	},
	/*
	 * returns the first slide id in an album.
	 */
	getAlbumSlideId : function (albumId) {
		var slideId;
		if (this.albumData[albumId] == undefined || this.albumData[albumId].slideId == undefined) {
			slideId = this.$slides.filter('[data-albumId="' + albumId + '"]').first().attr('data-slideid');
			if (this.albumData[albumId] == undefined) {
				this.albumData[albumId] = {};
			}
			this.albumData[albumId].slideId = slideId;
		} else {
			slideId = this.albumData[albumId].slideId;
		}
		return slideId;
	},
	/*
	 * insert the slide image when it has loaded
	 */
	insertSlides : function (imgSrc) {
		$('.lifestyle-image[data-src="' + imgSrc + '"]').attr('src', imgSrc).parent().removeClass('image-loading');
	},
	/*
	 * insert the thumbnail image when it has loaded
	 */
	insertThumbnails : function (imgSrc) {
		$('.thumbnail-image[data-src="' + imgSrc + '"]').attr('src', imgSrc).parent().removeClass('image-loading');
	},
	/*
	 * show the image loading animation
	 */
	showLoader : function (imgSrc) {
		$('img[data-src="' + imgSrc + '"]').parent().addClass('image-loading');
	},
	/*
	 * returns an array of image paths to load based on your position in the slideshow.
	 */
	getLoadQueue : function (slideIndex) {
		if (this.slideImages.length == this.slideImageQueue.completed.length) {
			this.imageLoadComplete = true;
		}
		var _self = this,
			imageSrcArray = [],
			x,
			max = 5,
			prevSlideIndex = Math.abs((slideIndex + _self.slideCount - max) % _self.slideCount),
			nextSlideIndex = (slideIndex + _self.thumbnailCount) % _self.slideCount;
		imageSrcArray.push(_self.slideImages[slideIndex]);
		for (x = 0; x <= max; x++) {
			imageSrcArray.push(_self.slideImages[(nextSlideIndex + x) % _self.slideCount]);
			imageSrcArray.push(_self.slideImages[(prevSlideIndex + x) % _self.slideCount]);
		}
		return imageSrcArray;
	},
	/*
	 * Returns an array of thumbnail image paths to load based on your position in the slideshow. 
	 * Thumbnais that are visible should load before ones off the screen.
	 */
	getThumbQueue : function (slideIndex) {
		if (this.thumbImages.length == this.thumbImageQueue.completed.length) {
			this.thumbLoadComplete = true;
		}
		var _self = this,
			imageSrcArray = [],
			x, y, z,
			max = 5,
			currPanelThumbIndex = slideIndex,
			prevPanelThumbIndex = Math.abs((slideIndex + _self.slideCount - _self.thumbnailCount) % _self.slideCount),
			nextPanelThumbIndex = (slideIndex + _self.thumbnailCount) % _self.slideCount;

		imageSrcArray.push(_self.slideImages[slideIndex]);

		for (x = 0; x < this.thumbnailCount; x++) {
			imageSrcArray.push(this.thumbImages[(currPanelThumbIndex + x) % this.slideCount]);
		}
		for (y = 0; y < this.thumbnailCount; y++) {
			imageSrcArray.push(this.thumbImages[(nextPanelThumbIndex + y) % this.slideCount]);
		}
		for (z = 0; z < this.thumbnailCount; z++) {
			imageSrcArray.push(this.thumbImages[(prevPanelThumbIndex + z) % this.slideCount]);
		}

		return imageSrcArray;
	},
	/*
	 * Ajax request for the lifestyle data. Once the request is complete the content is appended 
	 * to the slide and the lifestyleDisplay is initialized.
	 */
	loadRoom : function (slideId) {
		var _self = this,
			galleryURL = '/rooms/image-lifestyle-data.jsp?id=' + slideId;
		if (_self.slideLoadStatus[slideId] == undefined || _self.slideLoadStatus[slideId] == false) {
			_self.slideLoadStatus[slideId] = true;
			$.ajax({
				url: galleryURL,
				dataType: 'html',
				cache: true,
				success: function (data) {
					$('.lifestyle-container', '#slide-' + slideId).append(data).lifestyleDisplay();
				},
				error: function () {
					_self.slideLoadStatus[slideId] = false;
				}
			});
		}
	},
	/*
	 * Send in CM and GA tracking. For CM slides are tracked by album name (called gallery) and slide name.
	 */
	trackPageView : function () {
		var slideId = this.slideIdArray[this.slideIndex],
			slideName = this.getRoomName(slideId),
			albumId = this.getAlbumId(slideId),
			albumName = this.getAlbumName(albumId),
			cmRoom,
			cmGallery;
		if (this.lastSlideId != slideId) {
			cmRoom = (slideName != '') ? 'Room: ' + slideName : 'Room: ' + slideId;
			cmGallery = (albumName != '') ? 'Gallery: ' + albumName : 'Gallery: ' + albumId;
			try {
				cmCreatePageviewTag(cmRoom, null, cmGallery);
			} catch (e) {
				// coremetrics is off
			}
			try {
				_gaq.push(['_trackPageview', '/rooms/?id=' + albumId + '&slideid=' + slideId]);
			} catch (e) {
				// GA is off
			}
			this.lastSlideId = slideId;
		}
	},
	/*
	 * Returns the album name based off the id. Stores value in an albumData object to speed up future lookup.
	 */
	getAlbumName : function (albumId) {
		var albumName;
		if (this.albumData[albumId] == undefined || this.albumData[albumId].albumName == undefined) {
			albumName = $('a', this.$albumContainer).filter('[data-albumId="' + albumId + '"]').attr('data-title');
			if (this.albumData[albumId] == undefined) {
				this.albumData[albumId] = {};
			}
			this.albumData[albumId].albumName = albumName;
		} else {
			albumName = this.albumData[albumId].albumName;
		}
		return albumName;
	},
	/*
	 * Returns the slide name based off the id. Stores value in a slideData object to speed up future lookup.
	 */
	getSlideName : function (slideId) {
		var slideName;
		if (this.slideData[slideId] == undefined || this.slideData[slideId].slideName == undefined) {
			slideName = $(slideId).attr('data-title');
			if (this.slideData[slideId] == undefined) {
				this.slideData[slideId] = {};
			}
			this.slideData[slideId].slideName = slideName;
		} else {
			slideName = this.slideData[slideId].slideName;
		}
		return slideName;
	},
	/*
	 * Returns the album id of a slide. Gets this from a DOM element, and stores it in the slideData object to speed up future lookups.
	 */
	getAlbumId : function (slideId) {
		var albumId;
		if (this.slideData[slideId] == undefined || this.slideData[slideId].albumId == undefined) {
			albumId = $(slideId).attr('data-album');
			if (this.slideData[slideId] == undefined) {
				this.slideData[slideId] = {};
			}
			this.slideData[slideId].albumId = albumId;
		} else {
			albumId = this.slideData[slideId].albumId;
		}
		return albumId;
	}
};


/**
 * Creates a dark-overlay for placement behind loading bars or modals
 * @constructor
 * @private
 */
function Overlay() {
	this.showIframe = (($.browser.msie) && ($.browser.version == "6.0")) ? true : false;
	this.overlayDiv = $('<div class="overlay"></div>').css({'height': '100%', 'width': '100%', 'position': 'fixed', 'left': '0', 'top': '0', 'opacity': '.5', 'filter': 'alpha(opacity = 50)'});
	this.iframe = $('<iframe src="javascript:false; document.write(\'\');"></iframe>').css({'opacity': '0', 'filter': 'alpha(opacity = 0)', 'position': 'absolute', 'top': '0', 'left': '0', 'z-index': '-1', 'width': '100%', 'height': '100%' });
	this.state = false;
	return this;
}
Overlay.prototype = {
	show : function () {
		if (!this.state) {
			if (this.showIframe) {
				this.overlayDiv.css({'position': 'absolute', 'height': $(document).height(), 'width': '100%', 'z-index': 2999 + $('.overlay:visible').size() * 2}).prepend(this.iframe).prependTo('body');
			} else if ($.browser.SafariMobile) {
				this.overlayDiv.css({'position': 'absolute', 'height': $(document).height(), 'width': '100%', 'z-index': 2999 + $('.overlay:visible').size() * 2}).prependTo('body');
			} else {
				this.overlayDiv.css({'z-index': 2999 + $('.overlay:visible').size() * 2}).prependTo('body');
			}
			this.state = true;
		}
	},
	hide : function () {
		if (this.state) {
			this.overlayDiv.empty().remove();
			this.state = false;
		}
	},
	getEl : function () {
		return this.overlayDiv;
	}
};

/**
 * Creates an instance of a Loading Screen
 * @constructor
 * @private
 *
 */
function LoadingScreen() {
	this.loaderDiv = $('<div class="loading"></div>');
	this.state = false;
}
LoadingScreen.prototype = {
	/*
	 * Show the loading screen on a target element. if there is no target, append to body.
	 */
	show : function (targetEl) {
		if (!this.state) {
			var $target;
			if (targetEl !== undefined) {
				$target = $(targetEl);
				this.loaderDiv.css({'width': $target.width(), 'height': $target.height()});
			} else {
				$target = $('body');
			}
			this.loaderDiv.appendTo($target);
			this.state = true;
		}
	},
	/*
	 * Hide the loading screen.
	 */
	hide : function () {
		if (this.state) {
			this.loaderDiv.remove();
			this.loaderDiv.css({'width': '100%', 'height': '100%'});
			this.state = false;
		}
	},
	getEl : function () {
		return this.loaderDiv;
	}
};

/**
 *  Creates an instance of Modal Window
 *  @constructor
 *  @private
 */
function ModalWindow(options) {
	this.settings = {
		closeClass: 'close',
		title: false,
		loadingClass: 'loading'
	};
	if (options) {
		$.extend(this.settings, options);
	}
	this.overlayObj = new Overlay();
	this.overlayEl = this.overlayObj.getEl();
	this.loadingObj = new LoadingScreen();
	this.modalDiv = (this.settings.modalDivId != undefined) ? $(this.settings.modalDivId) : $('<div id="container-modal"></div>');
	this.status = false;
}

ModalWindow.prototype = {
	show : function (options) {
		var self = this;
		if (options != undefined && (options.url != undefined || options.content != undefined)) {
			if (!this.status) {
				this.overlayObj.show();
				this.showLoader(this.overlayEl);
				this.modalDiv.css({'z-index': 2999 + ($('.overlay:visible').size() * 2 - 1), 'visibility': 'hidden', 'display': 'block', 'top': '0', 'left': '0'}).insertAfter(this.overlayEl);
			} else {
				this.showLoader(this.modalDiv);
			}

			if (options.url != undefined) {
				$.ajax({
						url: options.url,
						success: function (pageData) {self.loadContent(pageData);},
						dataType: 'html',
						error: function (pageData) {self.loadContent(ajaxError);}
					});
			} else if (options.content != undefined) {
				self.loadContent(options.content);
			}
		} else {
			if (!this.status) {
				this.reposition();
				this.overlayObj.show();
				this.modalDiv.css({'z-index':3000 + $('.overlay:visible').size()*2}).insertAfter(this.overlayEl).show();
				this.overlayEl.click(function () {
					self.hide();
				});
				this.modalDiv.click(function (e) {
					var $target = $(e.target);
					if ($target.hasClass(self.settings.closeClass)) {
						self.hide();
						return false;
					}
				});
				this.status = true;
			}
		}
	},
	hide : function () {
		this.modalDiv.hide().empty();
		this.overlayObj.hide();
		this.status = false;
	},
	showLoader : function ($target) {
		if (!$target) {
			$target = this.modalDiv;
		}
		this.loadingObj.show($target);
	},
	hideLoader : function () {
		this.loadingObj.hide();
	},
	loadContent : function (data) {
		var self = this;
		if (!self.status) { // not already open
			self.modalDiv.html(data);
			self.reposition();
			self.loadingObj.hide();
			self.modalDiv.css({'visibility': 'visible'});
			self.overlayEl.click(function () {
				self.hide();
			});
			self.modalDiv.click(function (e) {
				var $target = $(e.target);
				if ($target.hasClass(self.settings.closeClass)) {
					self.hide();
					return false;
				}
			});
			self.status = true;
		} else {
			self.modalDiv.html(data);
			self.reposition();
			self.loadingObj.hide();
		}
	},
	reposition : function () {
		var $content = $('#content', this.modalDiv),
			$actions = $('.modal-actions', this.modalDiv),
			$title = $('.modal-title', this.modalDiv),
			vWidth = window.innerWidth ? window.innerWidth : $(window).width(),
			vHeight = window.innerHeight ? window.innerHeight : $(window).height(),
			overflow = $content.css('overflow'),
			modalWidth = this.modalDiv.width(),
			modalHeight = this.modalDiv.height(),
			contentWidth = $content.width(),
			contentHeight = $content.height(),
			actionsHeight = $actions.height(),
			titleHeight = $title.height(),
			marginLeft = '0',
			marginTop = '0',
			topPos = '50%',
			leftPos = '50%';
		if (modalWidth >= vWidth*0.9) {
			$content.css({'width':vWidth*0.9});
		}
		if (modalHeight >= vHeight*0.9 && overflow == 'auto') {
			if (vHeight*0.9 - actionsHeight - titleHeight > 130) {
				$content.css({'height':(vHeight*0.9 - actionsHeight - titleHeight)});
				if ($.browser.msie) {
					$content.css({'padding-right' : '20px', 'overflow-x' : 'hidden'});
				}
			} 
		} 
		if (modalHeight < vHeight) {
			marginLeft = 0-(this.modalDiv.width()/2);
			marginTop = 0-(this.modalDiv.height()/2)+$(document).scrollTop();
		} else {
			marginLeft = 0-(this.modalDiv.width()/2);
			if (overflow == 'auto') {
				topPos = $(document).scrollTop()+10;
			} else {
				topPos = $(document).scrollTop();
			}
		}
		this.modalDiv.css({'margin-left':marginLeft, 'margin-top':marginTop, 'top':topPos, 'left':leftPos});
	}
};

/**
 * Quick view object.
 * @constructor
 */
var quickView = {
	container:new ModalWindow(),
	$content:null,
	$trigger:$('<div id="quickview-trigger"><div id="quickview-screen"></div><a class="submit" >Quick View</a></div>'),
	triggerState:false,
	triggerID:0,

	/* display the quick view button */
	showLauncher: function (config) {
		var productId = config.productId,
			type = (config.type !== undefined) ? config.type:"",
			skuId = (config.skuId !== undefined) ? config.skuId:"",
			swatchId = (config.swatchId !== undefined) ? config.swatchId:"",
			el = config.el;
		if (!productId || !el)return false;
		if (this.triggerID != productId) {
			var offset = $(el).offset(),
				imgWidth=$(el).width(),
				imgHeight=$(el).height();
				el.boundingBox = {t:offset.top, r:offset.left + imgWidth, b:offset.top + imgHeight, l:offset.left};
			if (this.triggerState) {
				this.hideLauncher();
			}
			this.$trigger.appendTo(el);
			this.triggerState = true;
			this.triggerID = productId;
			this.$trigger.mouseout(function (e) {
				if (e && el.boundingBox && !mouseTest(e.pageX, e.pageY, el.boundingBox)) {
					return;
				}
				quickView.$trigger.unbind('mouseout');
				quickView.hideLauncher();
			});
			$(el).mouseout(function (e) {
				if (e && el.boundingBox && !mouseTest(e.pageX, e.pageY, el.boundingBox)) {
					return;
				}
				$(el).unbind('mouseout');
				quickView.hideLauncher();
			});
			this.$trigger.click(function () {
				quickView.launch({'productId':productId, 'type':type, 'skuId':skuId, 'swatchId':swatchId});
				quickView.hideLauncher();
			});
		}
	},
	/* hide the quick view button */
	hideLauncher: function () {
		quickView.$trigger.remove();
		quickView.triggerState = false;
		quickView.triggerID = 0;
	},
	/* launch load content and open quick view window */
	launch: function (config) {
		var qvUrl = '/modal/quickview.jsp?productId=';
		if (config.productId != null) {
			qvUrl += config.productId;
			qvUrl += '&type=' + config.type;

			if (config.skuId != "") {
				qvUrl += '&skuIds=' + config.skuId;
			}
			if (config.swatchId != "") {
				qvUrl += '&swatchId=' + config.swatchId;
			}
			if (dialog.status === true) {
				dialog.hide();
			}
			this.container.show({'url':qvUrl});
		} else {
			return false;
		}
	},
	close: function () {
		this.container.hide();
	},
	getStatus: function () {
		return this.container.status;
	}
};

/**
 * Product Zoom Object. Similar to the Quickview Object.
 * @constructor
 * TODO: careate a more generic constructor that these two objects can share.
 */
var productZoom = {
	container:new ModalWindow(),
	$content:null,
	$trigger:$('<div id="zoom-trigger"><a class="submit">Click to Zoom</a></div>'),
	triggerState:false,
	triggerID:'',

	/* display the zoom button */
	showLauncher: function (config) {
		var elId = config.elId,
			el = config.el,
			url = config.url;
		if (!elId || !el)return false;
		if (this.triggerID != elId) {
			var offset = $(el).offset(),
				imgWidth=$(el).width(),
				imgHeight=$(el).height();
				el.boundingBox = {t:offset.top, r:offset.left + imgWidth, b:offset.top + imgHeight, l:offset.left};
			if (this.triggerState) {
				this.hideLauncher();
			}
			this.$trigger.appendTo(el);
			this.triggerState = true;
			this.triggerID = elId;
			this.$trigger.mouseout(function (e) {
				if (e && el.boundingBox && !mouseTest(e.pageX, e.pageY, el.boundingBox)) {
					return;
				}
				productZoom.$trigger.unbind('mouseout');
				productZoom.hideLauncher();
			});
			$(el).mouseout(function (e) {
				if (e && el.boundingBox && !mouseTest(e.pageX, e.pageY, el.boundingBox)) {
					return;
				}
				$(el).unbind('mouseout');
				productZoom.hideLauncher();
			});
			this.$trigger.click(function () {
				productZoom.launch({'url':url});
				productZoom.hideLauncher();
			});
		}
	},
	/* hide the zoom button */
	hideLauncher: function () {
		productZoom.$trigger.remove();
		productZoom.triggerState = false;
		productZoom.triggerID = '';
	},
	/* launch load content and open zoom window */
	launch: function (config) {
		if (config.url != null) {
			if (dialog.status === true) {
				dialog.hide();
			}
			this.container.show({'url':config.url});
		} else {
			return false;
		}
	},
	close: function () {
		this.container.hide();
	},
	getStatus: function () {
		return this.container.status;
	}
};

// PRODUCT MODAL FUNCTIONS
function removeOverlay() {
	if (quickView.getStatus() !== true) {
		dialog.hide();
	} 
}
function showOverlay() {
	if (quickView.getStatus() !== true) {
		dialog.overlayObj.show();
		dialog.loadingObj.show(dialog.overlayEl);
	} else {
		quickView.container.showLoader();
	}
}

/**
 * determines the copy of instructional text given min 
 * and max values on the personalization fields
 * @returns {string} the instruction message
 */
function instructionalText(min, max) {
	var textOutput;
	if (min == max) {
		if (min == 1) {
			textOutput = '(enter 1 character)';
		} else {
			textOutput = '(enter ' + min + ' characters)';
		}
	} else {
		textOutput = '(enter ' + min + '-' + max + ' characters)';
	}
	if (max == 3) {
		textOutput += '<br/><span class="subtle">For a three-letter monogram with a large middle initial, that middle initial traditionally represents the last name. The first and the last initials represent the first name and middle (or maiden) name, respectively.</span>';
	} 
	return textOutput;
}
/**
 * shows text fields and instructions for personalization
 * @param {object} html element that contains the personalization form
 * @param {object} the event target, jquery element
 */
function selectScript(container, $target) {
	var $line1container = $('.line1', container),
		$line2container = $('.line2', container),
		$line3container = $('.line3', container),
		$line1 = $('input.line1-text', container),
		$line2 = $('input.line2-text', container),
		$line3 = $('input.line3-text', container),
		$line1instructions = $('.instructions', $line1container),
		$line2instructions = $('.instructions', $line2container),
		$line3instructions = $('.instructions', $line3container),
		$line1label = $('label', $line1container),
		$option = $('option:selected', $target),
		maxLength = $option.attr('data-maxLength'),
		maxLength1 = $option.attr('data-maxLength1') || maxLength,
		maxLength2 = $option.attr('data-maxLength2') || maxLength,
		maxLength3 = $option.attr('data-maxLength3') || maxLength,
		minLength = $option.attr('data-minLength'),
		numberOfLines = $option.attr('data-numberOfLines'),
		hasPreview = ($option.attr('data-haspreview') == 'true') ? true:false;

	// only show link if this script has a preview
	$('.instructions a', container).attr('href', '/modal/monogram-letters.jsp?monogramLayoutId=' + $target.val()).toggle(hasPreview);

	// delete values if previous specs does not equal new specs
	if ($line1.attr('minLength') !== undefined && $line1.attr('maxLength') != maxLength) {
		$line1.val('');
		$line2.val('');
		$line3.val('');
	}
	// set line 1 input to the maxLength & minLength
	$line1.attr('maxLength', maxLength1)
		.attr('minLength', minLength)
		.attr('size', parseInt(maxLength1)+2)
		.removeAttr('disabled'); // as long as a script is selected, always enable line 1

	// change out the line 1 instructional copy
	$line1instructions.html(instructionalText(minLength, maxLength1));
	// multi-line?
	$line1label.html('Text');
	if (numberOfLines == 1) {
		$line2container.hide();
		$line2.val('').attr('disabled', 'disabled');
		$line2instructions.empty();
		$line3container.hide();
		$line3.val('').attr('disabled', 'disabled');
		$line3instructions.empty();
	} else if (numberOfLines == 2) {
		$line2container.show();
		$line2.attr('maxLength', maxLength2).removeAttr('disabled');
		$line2instructions.html(instructionalText(0, maxLength2));
		$line3container.hide();
		$line3.val('').attr('disabled', 'disabled');
		$line3instructions.empty();
	} else if (numberOfLines == 3) {
		$line2container.show();
		$line2.attr('maxLength', maxLength2).removeAttr('disabled');
		$line2instructions.html(instructionalText(0, maxLength2));
		$line3container.show();
		$line3.attr('maxLength', maxLength3).removeAttr('disabled');
		$line3instructions.html(instructionalText(0, maxLength3));
	}
	$line1.focus();
}

/** 
 * Swatch Data Holder
 */
var SwatchModel = function (data) {
	var swatchData = (data) ? data:{};
	return {
		get: function (swatchId) {
			return swatchData[swatchId];
		}
	};
};

/* SWATCH CONTROLLER */
SwatchGroup = function () {

}
/** 
 * Product Image Controller. Controls product image colorization, and switching to alt views. 
 * Also updates the zoom link to reflect these changes
 * @constructor
 * @param {object} config
 */
var ProductImage = function (config) {
	var defaultThumbnail = 0;
	this.$imageContainer = config.$container;
	this.$zoomLink = $('.zoom-link', this.$imageContainer);
	this.$mainImage = $('.main-photo', this.$imageContainer);
	this.$altImages = $('.alternate-photo', this.$imageContainer);
	this.$thumbnails = (config.$thumbnails) ? $('li', config.$thumbnails) : {};
	this.isZoomEnabled = (this.$zoomLink.length > 0) ? true : false;
	this.defaultZoomUrl = (this.isZoomEnabled) ? this.$zoomLink.attr('href') : '';

	if (this.$thumbnails > 0) {
		defaultThumbnail = this.$thumbnails.index('.selected');
	}

	this.defaultThumbnail = defaultThumbnail;
};

ProductImage.prototype = {
	colorizeImage : function (url, swatchId) {
		this.$mainImage.attr('src', url);
		this.updateZoomLink(swatchId);
	},
	getZoomLink : function (swatchId) {
		var zoomUrl = this.$zoomLink.attr('href'),
			swatchRE = /&swatchId=[0-9a-zA-Z]+$/;
		if (zoomUrl.indexOf('&swatchId') > 0) {
			zoomUrl = zoomUrl.replace(swatchRE, '&swatchId=' + swatchId);
		} else {
			zoomUrl += '&swatchId=' + swatchId;
		}
		return zoomUrl;
	},
	updateZoomLink : function (swatchId) {
		var zoomUrl = this.getZoomLink(swatchId);
		this.$zoomLink.attr('href', zoomUrl);
	},
	switchImage : function (thumbIndex) {
		var zoomUrl,
			frameRE;
		if (this.$thumbnails.length > 0) {
			// change main image
			if (this.$mainImage.is(':visible')) {
				this.$mainImage.fadeOut('slow');
				$('#alt-photo' + thumbIndex, this.$imageContainer).fadeIn('slow');
			} else {
				this.$altImages.each(function (i, item) {
					if (i != thumbIndex) {
						$(item).fadeOut('slow');
					} else {
						$(item).fadeIn('slow');
					}
				});
			}
			// select thumbnail and reset swatch
			this.$thumbnails.removeClass('selected').find('#alt-photo-thumb' + thumbIndex).parent().addClass('selected');
			selectItemByIndex(-1, '#product-swatches .swatch-group li');
			// change zoom url
			if (this.isZoomEnabled) {
				zoomUrl = this.$zoomLink.attr('href');
				frameRE = /&initialframe=[0-9]+$/;
				if (thumbIndex != 0) {
					if (zoomUrl.indexOf('&initialframe=') > 0) {
						zoomUrl = zoomUrl.replace(frameRE, '&initialframe=' + thumbIndex);
					} else {
						zoomUrl += '&initialframe=' + thumbIndex;
					}
				} else if (thumbIndex == 0) {
					zoomUrl = zoomUrl.replace(frameRE, '');
				}
				this.$zoomLink.attr('href', zoomUrl);
			}
			// track click
			try {
				cmCreatePageviewTag(cmProduct + ' (image-' + thumbIndex + ')', null, 'Additional Images');
			} catch (ex) {
				// cm disabled
			}
		}
	},
	resetImage : function () {
		if (this.$thumbnails.length > 0) {
			if (this.$mainImage.is(':visible') != true) {
				this.$altImages.fadeOut('slow');
				this.$mainImage.fadeIn('slow');
				selectItemByIndex(this.defaultThumbnail, this.$thumbnails);
				selectItemByIndex(-1, '#product-swatches .swatch-group li');
			}
		}
		if (this.isZoomEnabled) {
			this.$zoomLink.attr('href', this.defaultZoomUrl);
		}
	}
};

/**
 * The Product Model handles the sku data for a product
 * @constructor
 */
var ProductModel = function (data) {
	this.productData = (data) ? data:{};
	this.availability = {}; 
};
ProductModel.prototype = {
	/*
	 * Loop through the product's sku data and elimates skus that do not have enough options.
	 */
	cleanSkuData :function () {
		for (product in this.productData) {
			var maxOptionSize = 0;
			var optionSize = 0;
			var badSkus = [];
			// find option length
			for (var x = 0; x < this.productData[product].skus.length; x++) {
				optionSize = Object.size(this.productData[product].skus[x].attributes);
				this.productData[product].skus[x].optionSize = optionSize;
				if (optionSize > maxOptionSize) {
					maxOptionSize = optionSize;
				}
			}
			// find bad skus
			for (var x = 0; x < this.productData[product].skus.length; x++) {
				if (this.productData[product].skus[x].optionSize < maxOptionSize) {
					badSkus.push(x);
				}
			}
			// kill the bad skus
			if (maxOptionSize == 0 && this.productData[product].skus.length > 1) {
				this.productData[product].skus = [];
			} else {
				for (var y = 0; y < badSkus.length; y++) {
					this.productData[product].skus.splice(badSkus[y]-y, 1);
				}
			}
		}
	},
	get: function (prodId) {
		return this.productData[prodId];
	},
	getSku: function (prodId, skuId) {
		var thisProduct = this.get(prodId);
		for (i = 0; i < thisProduct.skus.length; i++) {
			if (thisProduct.skus[i].catalogRefId == skuId) {
				return thisProduct.skus[i];
			}
		}
	},
	getColorizeType : function (prodId) {
		return this.productData[prodId].colorizeType;
	},
	getPrimarySwatchOptions : function (prodId) {
		if (this.productData[prodId].primarySwatchOptions !== undefined) {
			return this.productData[prodId].primarySwatchOptions;
		} else {
			return false;
		}
	},
	getSecondarySwatchOptions : function (prodId) {
		if (this.productData[prodId].secondarySwatchOptions !== undefined) {
			return this.productData[prodId].secondarySwatchOptions;
		} else {
			return false;
		}
	},
	/*
	 * Given a product id, return an array of option types (size, color, etc)
	 */
	getOptionTypes : function (prodId) {
		var firstSkuOptions = this.getSkuOptions(prodId, this.productData[prodId].skus[0].catalogRefId),
			optionTypeArray = [],
			sortedOptionTypeArray = [],
			i,
			optionType;
		
		for (optionType in firstSkuOptions) {
			optionTypeArray.push(optionType);
		}
		if (this.productData[prodId].optionOrder != "") {
			for (i = 0; i < this.productData[prodId].optionOrder.length; i++) {
				if (optionTypeArray.contains(this.productData[prodId].optionOrder[i])) {
					sortedOptionTypeArray.push(this.productData[prodId].optionOrder[i]);
				}
			}
			sortedOptionTypeArray.concat(optionTypeArray);
			sortedOptionTypeArray.dedup();
			return sortedOptionTypeArray;
		} else {
			return optionTypeArray;
		}
	},
	/*
	 * Get all the available options for a product.
	 */
	getAllOptions: function (prodId) {
		var thisProduct = this.productData[prodId],
			optionTypeArray = this.getOptionTypes(prodId),
			options = {},
			optionType = '',
			optionKey = '',
			sortedSkuArray = [],
			firstChar = '',
			i, x;
		for (i = 0; i < optionTypeArray.length; i++) { // for each type
			optionType = optionTypeArray[i];
			firstChar = thisProduct.skus[0].attributes[optionType].displayName.charAt(0);
			if (firstChar != '$' ) {
				sortedSkuArray = thisProduct.skus.sort(prioritySort(optionType));
			} else {
				sortedSkuArray = thisProduct.skus.sort(displayNameSort(optionType));
			}
			/* loop though the skus */
			for (x = 0; x < sortedSkuArray.length; x++) {
				/* make sure this option exists  for  the sku */
				if (sortedSkuArray[x].attributes[optionType] !== undefined) { 
					optionKey = sortedSkuArray[x].attributes[optionType].optionId;
					/* see if we have an object for this type */
					if (options[optionType] === undefined) { 
						/* create and populate the object */
						options[optionType] = {}; 
						options[optionType][optionKey] = sortedSkuArray[x].attributes[optionType];
					/* An object exists for this type so see if this value already exists. If not, populate it. */
					} else if (options[optionType][optionKey] === undefined) { 
						options[optionType][optionKey] = sortedSkuArray[x].attributes[optionType];
					}
				}
			}
		}
		return options;
	},
	/*
	 * For each selector (option type), filter down the matching skus for
	 * the other selectors' selected elements. From the filtered sku set,
	 * get back all available options for this option type
	 */
	getFilteredOptions: function (prodId, selectedOptions) {
		var optionTypeArray = this.getOptionTypes(prodId),
			tmpArray = [],
			allSkus = this.productData[prodId].skus,
			filteredSkuArray,
			options = {},
			optionType = '',
			optionKey = '',
			i, j, k, selectedOptionType;

		for (i = 0; i < optionTypeArray.length; i++) {
			optionType = optionTypeArray[i];
			filteredSkuArray = allSkus;
			for (selectedOptionType in selectedOptions) {
				if (selectedOptionType != optionType) {
					for (j = 0; j < filteredSkuArray.length; j++) {
						if (filteredSkuArray[j].attributes[selectedOptionType] !== undefined) {
							if (filteredSkuArray[j].attributes[selectedOptionType].optionId == selectedOptions[selectedOptionType]) {
								tmpArray.push(filteredSkuArray[j]);
							}
						}
					}
					filteredSkuArray = tmpArray;
					tmpArray = [];
				}
			}
			for (k = 0; k < filteredSkuArray.length; k++) {
				if (filteredSkuArray[k].attributes[optionType] !== undefined) {
					optionKey = filteredSkuArray[k].attributes[optionType].optionId;
					if (options[optionType] === undefined) { 
						options[optionType] = {};
						options[optionType][optionKey] = filteredSkuArray[k].attributes[optionType]; 
					} else if (options[optionType][optionKey] === undefined) { 
						options[optionType][optionKey] = filteredSkuArray[k].attributes[optionType]; 
					}
				}
			}
		}
		return options;
	},
	/*
	 * Get the options for a sku. Returns key value pairs where key is the option type and the value is the option id.
	 */
	getSkuOptions: function (prodId, skuId) {
		var thisProduct = this.get(prodId),
			options = {},
			i, j;
		for (i = 0; i < thisProduct.skus.length; i++) {
			if (thisProduct.skus[i].catalogRefId == skuId) {
				for (j in thisProduct.skus[i].attributes) {
					if (options[j] === undefined) {
						options[j] = thisProduct.skus[i].attributes[j].optionId;
					}
				}
				break;
			}
		}
		return options;
	},
	/*
	 * Based on selected options, return an array of matching skus.
	 */
	getFilteredSkus: function (prodId, selectedOptions) {
		var tempArray = [],
			tmpSkuArray = this.productData[prodId].skus,
			optionType,
			i;
		for (optionType in selectedOptions) {
			for (i = 0; i < tmpSkuArray.length; i++) {
				if (tmpSkuArray[i].attributes[optionType] !== undefined && tmpSkuArray[i].attributes[optionType].optionId == selectedOptions[optionType]) {
					tempArray.push(tmpSkuArray[i]);
				}
			}
			tmpSkuArray = tempArray;
			tempArray = [];
		}
		return tmpSkuArray;
	},
	/* legacy, remove */
	isZipRequired : function (ids) {
	},
	/* If all products are disabled, disable add to cart */
	isAddToCartEnabled : function () {
		var enabled = false;
		for (product in this.productData) {
			if (this.productData[product].disabled == 'false') {
				enabled = true;
				break;
			}
		}
		return enabled;
	},
	setDisabled : function (prodId) {
		this.productData[prodId].disabled = 'true';
	},
	/* 
	 * Get the stored sku availability. If there there is not stored value, return false. 
	 */
	getSkuAvailability : function (skuId) {
		var jsonResponse, 
			availabilityMsg;
		if (this.availability[skuId] === undefined || this.availability[skuId].inventoryCode === '') {
			return false;
		}
		return this.availability[skuId];
	},
	/* 
	 * Do the sku availability lookup. Store this response locally.
	 */
	setSkuAvailability : function (skuId, productController, skuGroup, updateQty) {
		var THIS = this;
		$.ajax({
			url: '/sitewide/data/json/sku-availability.jsp?skuId=' + skuId,
			dataType: 'json',
			success: function (data) {
				THIS.availability[skuId] = data;
				productController.updateAvailability(skuGroup, updateQty);
			},
			error: function () {
				productController.showInventoryError(skuGroup, updateQty);
			}
		});
	},
	/* 
	 * Reset the stored availability values, for postal code changes.
	 */
	resetSkuAvailability : function () {
		var k;
		for (k in this.availability) {
			if (this.availability[k].postalCodeSpecific == 'true' || this.availability[k].deliveryCode != '200') {
				// reset inventory values
				this.availability[k].inventoryCode = '';
				this.availability[k].inventoryMessage = '';
			}
		}
	},
	/* Sets default skus on products. Loop thought the products and see if it contains the sku. 
	 * if so set that sku as a default. Used if we pass in skus on the url. 
	 */
	setDefaults : function (skuList) {
		var i, j, product;
		for (i = 0; i < skuList.length; i++) {
			outer: for (product in this.productData) {
				for (j = 0; j < this.productData[product].skus.length; j++) {
					if (skuList[i] == this.productData[product].skus[j].catalogRefId) {
						this.productData[product].defaultSku = skuList[i];
						break outer;
					}
				}
			}
		}
	}
};
/**
 * Sku group defines the UI interface elements for a single product
 * @constructor
 * @private
 */
var SkuGroup = function (container) {
	// dom
	this.$container = $(container);
	this.$image = $('.item-image img', this.$container);
	this.$zoomLink = $('.item-image a', this.$container);
	this.$qtyInput = $('.qtyInput', this.$container);
	this.$personalizeInput = $('.personalizeInput', this.$container);
	this.$sku = $('.item-sku', this.$container);
	this.$price = $('.item-price', this.$container);
	this.$details = $('.details', this.$container);
	this.$info = $('.item-info', this.$container);
	this.$selectors = [];
	// data
	this.prodId = this.$container.attr('data-productId');
	this.enabled = false;
	this.catalogRefId = '';
	this.origImage = (this.$image.length > 0 ) ? this.$image.attr('src'):'',
	this.imgBase = (this.$image.length > 0 ) ? this.origImage.substring(this.origImage.indexOf(s7data.staticHost) + s7data.staticHost.length + 1, this.origImage.indexOf('?')):'';
	
	// new photo controller
	this.$productPhoto = $('.item-image', this.$container);
	this.photoController = null;
	if (this.$productPhoto.length > 0) {
		this.photoController = new ProductImage({$container : this.$productPhoto});
	}
};


/**
 * Product Controller defines the interaction with the product page. Individual products are skuGroups.
 * Requires jquery.form plugin.
 * @constructor
 * @param {object} productModel
 * @param {object} swatchModel
 * @param {object} viewContainer
 * @param {array} skuIds
 * @see SkuGroup
 * @see ProductImage
 */
var ProductController = function (productModel, swatchModel, viewContainer, skuIds) {
	this.model = productModel;
	this.swatchModel = swatchModel;
	this.$viewContainer = $(viewContainer);
	this.$swatchContainer = $('#product-swatches', this.$viewContainer);
	this.$thumbContainer = $('#product-photo-thumbs', this.$viewContainer);
	this.$zoomLink = $('#product-photo a', this.$viewContainer);
	this.$altPhotos = $('.alternate-photo', this.$viewContainer);
	this.$productContainer = $("#line-items", this.$viewContainer);
	this.$errorContainer = $('#error-zone-top', this.$productContainer);
	this.$productControls = $('#product-controls', this.$productContainer);
	this.$formContainer = $('#product-form', this.$productContainer);
	this.$lineItems = $('.line-item', this.$productContainer);
	this.$addButton = $('#add-button', this.$productControls);
	this.$postalCodeForm = $('#postalcodeform', this.$productControls);
	this.skuGroups = [];
	this.initialSkuList = skuIds;
	this.postalCodeLabel = 'Enter Zip/Postal Code';
	this.postalCodeUpdateLabel = 'Change Zip/Postal Code';
	this.postalCodeInputs = '<input type="text" id="postal-code" class="postal-code" size="6" maxlength="6" autocomplete="off" tabindex="0" value=""/><input type="submit" value="Enter" class="submit postal-trigger"/>';
	this.standardDeliveryLink = '<a href="/modal/shipping-and-delivery-rates.jsp?tab=2" title="Shipping &amp; Delivery Information" class="modal-trigger delivery-standard" rel="shipping-modal">Standard Delivery Shipping</a>';
	this.furnitureDeliveryLink = '<a href="/modal/shipping-and-delivery-rates.jsp?tab=1" title="Shipping &amp; Delivery Information" class="modal-trigger delivery-furniture" rel="shipping-modal">Unlimited Furniture Delivery</a>';

	/* 
	 * new photo controller for the main product photo
	 */
	this.$productPhoto = $('#product-imagery', this.$viewContainer);
	this.photoController = null;
	if (this.$productPhoto.length > 0) {
		this.photoController = new ProductImage({$container : this.$productPhoto, $thumbnails : this.$thumbContainer});
	}

	var THIS = this;
	
	/* 
	 * event listeners
	 */
	this.$lineItems.change(function (e) {
		var $target = $(e.target),
			index;
		/* product configuration dropdown change */
		if ($target.hasClass('attribute')) {
			index = $target.attr('data-index');
			THIS.changeAttribute(index, $target);
		}
	}).click(function (e) {
		var $target = $(e.target),
			index;
		/* Add to cart trigger, from line item */
		if ($target.hasClass('add-trigger')) {
			index = $target.attr('data-index');
			selectedSkus = THIS.getSelectedSkus(index);
			if (selectedSkus !== false) {
				itemCount = selectedSkus.length;
				$.ajax({
					url: '/catalog/product/includes/product-form-handler.jsp?type=cart&itemCount=' + itemCount,
					dataType: 'html',
					cache: false,
					success: function (data) {
						THIS.$formContainer.html(data);
						$('#addToCart').ajaxSubmit({ 
							clearForm: false,
							dataType: 'html', 
							productGroup: THIS,
							skuArray : selectedSkus,
							success: THIS.handleResponse,
							beforeSerialize: THIS.addToCartUpdate,
							beforeSubmit: showOverlay,
							error: THIS.showError
						});
					},
					error: function () {
						THIS.showError;
					}
				});
			}
		/* Submit postal code update */
		} else if ($target.hasClass('postal-trigger')) {
			var postalCode = $target.siblings('#postal-code').val();
				postalCode = $.trim(postalCode);
			$.ajax({
				url: '/catalog/product/includes/product-form-handler.jsp?type=postal&itemCount=0&postalCode=' + postalCode,
				dataType: 'html',
				cache: false,
				success: function (data) {
					THIS.$formContainer.html(data);
					$('#addToCart').ajaxSubmit({ 
						clearForm: false,
						dataType: 'html', 
						productGroup: THIS,
						success: THIS.handleResponse,
						beforeSubmit: showOverlay,
						error: THIS.showError
					});
				},
				error: function () {
					THIS.showError;
				}
			});
		/* Postal code change - show the postal code form again */
		} else if ($target.hasClass('postalform-trigger-delivery')) {
			$('.item-delivery-message', this).find('.postalcodeform-inline').toggle().end().find('.postalcode-update').toggle();
		} else if ($target.hasClass('postalform-trigger-inventory')) {
			$('.item-inventory-message', this).find('.postalcodeform-inline').toggle().end().find('.postalcode-update').toggle();
		}
	}).keypress(function (e) {
		var $target = $(e.target);
		/* Submit postal code form with enter keypress */
		if ($target.hasClass('postal-code')) {
			if (e.which == 13) {
				if ($target.val() != '') {
					var postalCode = $target.val();
						postalCode = $.trim(postalCode);
					$.ajax({
						url: '/catalog/product/includes/product-form-handler.jsp?type=postal&itemCount=0&postalCode=' + postalCode,
						dataType: 'html',
						cache: false,
						success: function (data) {
							THIS.$formContainer.html(data);
							$('#addToCart').ajaxSubmit({ 
								clearForm: false,
								dataType: 'html', 
								productGroup: THIS,
								success: THIS.handleResponse,
								beforeSubmit: showOverlay,
								error: THIS.showError
							});
						},
						error: function () {
							THIS.showError;
						}
					});
				} else {
					THIS.showError(errorMessages.missingPostal);
					return false;
				}
			}
		}
	});
	/* Swatch click. 
	 * See ProductController.swatchSelect.
	 */
	this.$swatchContainer.click(function (e) {
		var $target = $(e.target);
		if ($target.hasClass('swatch')) {
			THIS.swatchSelect($target);
		}
	})
	/* If not a touch device, hook up the hover event for the swatches. 
	 * See ProductController.swatchOver and ProductController.swatchOut.
	 */
	if (!Modernizr.touch) {
		this.$swatchContainer.mouseover(function (e) {
			var $target = $(e.target);
			if ($target.hasClass('detail-trigger')) {
				THIS.swatchOver($target);
			}
		}).mouseout(function (e) {
			var $target = $(e.target);
			if ($target.hasClass('detail-trigger')) {
				THIS.swatchOut($target);
			}
		});
	};
	/* Click events on the product thumbnails.
	 * See productImage.switchImage and ProductImage.resetImage.
	 */
	this.$thumbContainer.click(function (e) {
		var $target = $(e.target),
			thumbIndex;
		if ($target.hasClass('alt')) {
			thumbIndex = $target.attr('data-index');
			THIS.photoController.switchImage(thumbIndex);
		} else if ($target.hasClass('reset')) {
			THIS.photoController.resetImage();
		}
	});
	/* Click events on the product form buttons (wishlist, registry, add to cart)
	 * TODO: these are very similar, roll them into a single function
	 */
	this.$productControls.click(function (e) {
		var targetId = e.target.id,
			selectedSkus = [],
			registryId = '';
		/* 
		 * The Add to cart action
		 */
		if (targetId == 'add-button') {
			selectedSkus = THIS.getSelectedSkus();
			if (selectedSkus !== false) {
				itemCount = selectedSkus.length;
				var searchParam = '';
				if (categoryId == 'search') {
					searchParam = '&searchProductId=' + productId;
				}
				/* request the add to cart form */
				$.ajax({
					url: '/catalog/product/includes/product-form-handler.jsp?type=cart&itemCount=' + itemCount+searchParam,
					dataType: 'html',
					cache: false,
					success: function (data) {
						THIS.$formContainer.html(data);
						$('#addToCart').ajaxSubmit({ 
							clearForm: false,
							dataType: 'html', 
							productGroup: THIS,
							skuArray : selectedSkus,
							success: THIS.handleResponse, /* show the confirmation popup */
							beforeSerialize: THIS.addToCartUpdate, /* populate the form fields */
							beforeSubmit: showOverlay, /* Show the overlay to prevent multiple submits */
							error: THIS.showError
						});
					},
					error: function () {
						THIS.showError;
					}
				});
			}
		} else if (targetId == 'wishlist-button') {
			selectedSkus = THIS.getSelectedSkus();
			if (selectedSkus !== false) {
				itemCount = selectedSkus.length;
				/* request the wishlist form */
				$.ajax({
					url: '/catalog/product/includes/product-form-handler.jsp?type=wishlist&itemCount=' + itemCount,
					dataType: 'html',
					cache: false,
					success: function (data) {
						THIS.$formContainer.html(data);
						$('#addToCart').ajaxSubmit({ 
							clearForm: false,
							dataType: 'html', 
							productGroup: THIS,
							skuArray : selectedSkus,
							success: THIS.handleResponse, /* show the confirmation popup */
							beforeSerialize: THIS.addToGiftListUpdate, /* populate the form fields */
							beforeSubmit: showOverlay, /* Show the overlay to prevent multiple submits */
							error: THIS.showError
						});
					},
					error: function () {
						THIS.showError;
					}
				});
			}
		} else if (targetId == 'registry-button') {
			selectedSkus = THIS.getSelectedSkus();
			registryId = $('#registryId', this).val();
			if (selectedSkus !== false) {
				itemCount = selectedSkus.length;
				/* request the gift list (registry) form */
				$.ajax({
					url: '/catalog/product/includes/product-form-handler.jsp?type=giftlist&itemCount=' + itemCount + '&registryId=' + registryId,
					dataType: 'html',
					cache: false,
					success: function (data) {
						THIS.$formContainer.html(data);
						$('#addToCart').ajaxSubmit({ 
							clearForm: false,
							dataType: 'html', 
							productGroup: THIS,
							skuArray : selectedSkus,
							success: THIS.handleResponse, /* show the confirmation popup */
							beforeSerialize: THIS.addToGiftListUpdate, /* populate the form fields */
							beforeSubmit: showOverlay, /* Show the overlay to prevent multiple submits */
							error: THIS.showError
						});
					},
					error: function () {
						THIS.showError;
					}
				});
			}
		/* TODO: Re-check. This may be legacy code, from when we replaced the add to cart button with the postal code form */
		} else if (targetId == 'postal-button') {
			itemCount = 0;
			postalCode = $('#postal-code', this).val();
			/* request the postal code form */
			$.ajax({
				url: '/catalog/product/includes/product-form-handler.jsp?type=postal&itemCount=' + itemCount + '&postalCode=' + postalCode,
				dataType: 'html',
				cache: false,
				success: function (data) {
					THIS.$formContainer.html(data);
					$('#addToCart').ajaxSubmit({ 
						clearForm: false,
						dataType: 'html', 
						productGroup: THIS,  /* show the confirmation popup */
						success: THIS.handleResponse, /* populate the form fields */
						beforeSubmit: showOverlay, /* Show the overlay to prevent multiple submits */
						error: THIS.showError
					});
				},
				error: function () {
					THIS.showError;
				}
			});
		}
	});
};

ProductController.prototype = {
	/*
	 * Initialization.
	 */
	init : function () {
		var THIS = this,
			prodId,
			skuGroup,
			prodData,
			selectorGroup,
			defaultSku = '',
			state,
			style,
			defaultSelectedOptions = {},
			skuList = [],
			x = 0,
			optionType,
			allOptions,
			availableOptions,
			option;
		/* default skus */
		if (this.initialSkuList != '') {
			skuList = this.initialSkuList.split("+");
			this.model.setDefaults(skuList);
		}

		/* create skuGroup objects */
		this.$lineItems.each(function (i) {
			var skuGroup = new SkuGroup(this);
			THIS.skuGroups.push(skuGroup);
		});
		/* initialize and update availability */
		for (x = 0; x < this.skuGroups.length; x++) {
			prodId = this.skuGroups[x].prodId;
			skuGroup = this.skuGroups[x];
			prodData = THIS.model.get(prodId);
			if (prodData && prodData.skus.length != 0) {
				if (prodData.defaultSku != '') {
					defaultSelectedOptions = THIS.model.getSkuOptions(prodId, prodData.defaultSku);
					defaultSku = prodData.defaultSku;
				} else if (prodData.skus.length == 1) {
					defaultSelectedOptions = THIS.model.getSkuOptions(prodId, prodData.skus[0].catalogRefId);
					defaultSku = prodData.skus[0].catalogRefId;
				} else {
					defaultSelectedOptions = {};
					defaultSku = '';
				}
				allOptions = THIS.model.getAllOptions(prodId);
				availableOptions = THIS.model.getFilteredOptions(prodId, defaultSelectedOptions);
				selectorGroup = '';
				
				for (optionType in allOptions) {
					optionCount = 0;
					/* get length before doing any other processing */
					for (option in allOptions[optionType]) {
						optionCount++; 
					}
					/* if there's more than one option, create the dropdown. Otherwise just print out the option. */
					if (optionCount > 1) {
						selectorGroup += '<fieldset><label class="label">' + optionType + '<\/label><select class="attribute" data-attribute="' + optionType + '" data-index = "' + x + '">';
						selectorGroup += '<option value="-1">Choose a ' + optionType + '<\/option>';
						for (option in allOptions[optionType]) {
							state = (defaultSelectedOptions[optionType] !== undefined && defaultSelectedOptions[optionType] == option) ? 'selected':'';
							style = (availableOptions[optionType] === undefined || availableOptions[optionType][option] === undefined) ? 'class="disabled"':'';
							disable = (availableOptions[optionType] === undefined || availableOptions[optionType][option] === undefined) ? 'disabled="disabled"':'';
							selectorGroup += '<option value="' + option + '" ' + state + style + disable + '>' + allOptions[optionType][option].displayName + '<\/option>';
						}
						selectorGroup += '<\/select><\/fieldset>';
					} else {
						selectorGroup += '<fieldset><label class="label">' + optionType + '<\/label>';
						for (option in allOptions[optionType]) {
							selectorGroup += '<div class="read-only">' + allOptions[optionType][option].displayName + '<input type="hidden" class="attribute" value="' + option + '" data-attribute="' + optionType + '"/></div>';
						}
						selectorGroup += '<\/fieldset>';
					}
				}
				/* if there are attributes, add them to the line item */
				if (selectorGroup != '') {
					skuGroup.$info.prepend('<div class="item-selectors group">' + selectorGroup + '<\/div>');
					skuGroup.$selectors = $('.attribute', skuGroup.$container);
				}
				/* Show the personalization form if the checkbox is selected. */
				if (skuGroup.$personalizeInput.length > 0) {
					if (skuGroup.$personalizeInput.is(':checked')) {
						skuGroup.$container.find('.personalize-form').css('display', 'block');
					}
				}
				/* If there's a default sku for the product, select it's attributes. Otherwise, disable the qty input. */
				if (defaultSku !== '') {
					skuGroup.catalogRefId = defaultSku;
					this.updateAvailability(skuGroup, false);
					this.colorizeSkuGroupImage(skuGroup, prodId, defaultSelectedOptions, defaultSku);
				} else {
					skuGroup.catalogRefId = '';
					skuGroup.$qtyInput.attr('disabled', 'true');
				}
				skuGroup.enabled = true;
			} else {
				/* No skus. Disable the product and hide it from the user.*/
				skuGroup.enabled = false;
				THIS.model.setDisabled(prodId);
				skuGroup.$container.hide();
			}
		}
		/* If none of the products are available, hide the add to cart button.
		 * TODO: what about wishlist and registry?
		 */
		if (THIS.model.isAddToCartEnabled() == false) {
			THIS.$addButton.hide();
		}
	},
	/**
	 * changeAttribute is called when the user changes one of the product option dropdowns.
	 * Based on the index of the sku group (which line item this is) and the select element 
	 * that triggered the change, this function creates a map of selected options and passes 
	 * it to the updateSelectors function.
	 * 
	 * @param {int} index
	 * @param {object} $selectElement
	 * @see ProductController.updateSelectors
	 * 
	 */
	changeAttribute: function (index, $selectElement) {
		var THIS = this,
			skuGroup = this.skuGroups[index],
			prodId = skuGroup.prodId,
			optionType = '',
			optionId = '',
			triggerOptionType = $selectElement.attr('data-attribute'),
			triggerOptionId = $selectElement.val(),
			selectedOptions = {},
			filteredSkuArray,
			autoSelect = true,
			i;
		/* Get the selected options from each dropdown. If the trigger select item is disabled, 
		 * then we need to deselct one or more of the other selected options. Don't include the 
		 * option in the selectedOptions map if it is -1: "Choose an [option]".
		 */
		if ($("option:selected", $selectElement).attr('disabled')) {
			autoSelect = false;
			selectedOptions[triggerOptionType] = triggerOptionId;
			filteredSkuArray = THIS.model.getFilteredSkus(prodId, selectedOptions);
			skuGroup.$selectors.each(function () {
				optionType = $(this).attr('data-attribute');
				optionId = $(this).val();
				if (optionType != triggerOptionType) {
					for (i = 0; i < filteredSkuArray.length; i++) {
						if (filteredSkuArray[i].attributes[optionType].optionId == optionId) {
							selectedOptions[optionType] = optionId;
							break;
						}
					}
				} 
			});
		} else {
			skuGroup.$selectors.each(function () {
				optionType = $(this).attr('data-attribute');
				optionId = $(this).val();
				if (optionId != "-1") {
					selectedOptions[optionType] = optionId;
				}
			});
		}
		/* Don't use the auto select option if the trigger select is -1 */
		if (triggerOptionId == '-1') {
			this.updateSelectors({'skuGroup':skuGroup, 'prodId':prodId, 'selectedOptions':selectedOptions, 'triggerOptionType':triggerOptionType});
		} else {
			this.updateSelectors({'skuGroup':skuGroup, 'prodId':prodId, 'selectedOptions':selectedOptions, 'triggerOptionType':triggerOptionType, 'autoSelect':autoSelect, 'updateQty':true});
		}
		
	},
	/**
	 * Loops though each product (skuGroup) and fires selectSkuGroupOptions using the selected swatch's options
	 * @param {object} swatchOptions
	 * @see ProductController.selectSkuGroupOptions
	 */
	selectSwatchAttributes : function (swatchOptions) {
		for (var x = 0; x < this.skuGroups.length; x++) {
			if (this.skuGroups[x].enabled !== false) {
				this.selectSkuGroupOptions(this.skuGroups[x], swatchOptions);
			} 
		}
	},
	/**
	 * Given a map of options, figures out which options can be selected for a product (skuGroup). Not all option 
	 * types or option combinations may be available so this function figures out which ones can be selected, then 
	 * fires off the updateSelectors function with the selectedOptions.
	 * @param {object} skuGroup
	 * @param {object} options
	 * @see ProductController.updateSelectors
	 */
	selectSkuGroupOptions : function (skuGroup, options) {
		var THIS = this,
			prodId = skuGroup.prodId,
			productOptionTypes = THIS.model.getOptionTypes(prodId),
			selectedOptions = {},
			match = false,
			filteredSkuArray,
			optionId,
			optionType,
			swatchOptionType,
			x, i, j, y, z,
			$selector;

		for (i = 0; i < productOptionTypes.length; i++) {
			if (options[productOptionTypes[i]] !== undefined) {
				match = true;
				break;
			}
		}
		if (match !== false) {
			filteredSkuArray = THIS.model.get(prodId).skus;
			for (swatchOptionType in options) {
				optionId = options[swatchOptionType];
				for (j = 0; j < filteredSkuArray.length; j++) {
					if (filteredSkuArray[j].attributes[swatchOptionType] != undefined && filteredSkuArray[j].attributes[swatchOptionType].optionId == optionId) {
						selectedOptions[swatchOptionType] = optionId;
						filteredSkuArray = THIS.model.getFilteredSkus(prodId, selectedOptions);
						break;
					}
				}
			}
			for (y = 0; y < skuGroup.$selectors.length; y++) {
				$selector = $(skuGroup.$selectors[y]);
				optionType = $selector.attr('data-attribute');
				if (options[optionType] === undefined) {
					optionId = $selector.val();
					for (z = 0; z < filteredSkuArray.length; z++) {
						if (filteredSkuArray[z].attributes[optionType].optionId == optionId) {
							selectedOptions[optionType] = optionId;
							filteredSkuArray = THIS.model.getFilteredSkus(prodId, selectedOptions);
							break;
						}
					}
				}
			}
			THIS.updateSelectors({'skuGroup':skuGroup, 'prodId':prodId, 'selectedOptions':selectedOptions, 'autoSelect':true});
		}
	},
	/**
	 * The select boxes for the products are multi-directional. That is, you can select them in any order. Each 
	 * selection narrows the choices of the other dropdowns.If the options have been narrowed down to a single sku, 
	 * the updateAvailability function is called. Otherwise, the resetAvailability is called to remove data from 
	 * any previously selected items. Also triggers the colorization on the line-item product image.
	 * 
	 * @param {object} options Configurations.
	 * @config {object} skuGroup The skuGroup object this change applies to.
	 * @config {string} prodId The id of this product.
	 * @config {object} selectedOptions The selected options as key value pairs, where the key is the optionType.
	 * @config {boolean} [autoSelect] If true, auto-select other options if narrowed to 1 available choice. (cascades)
	 * @config {boolean} [updateQty] If true, update the qty to 1
	 * @config {string} [triggerOptionType] Which optionType triggered the update
	 * @see ProductController.updateAvailability
	 * @see ProductController.resetAvailability
	 * @see ProductController.colorizeSkuGroupImage
	 */
	updateSelectors : function (options) {
		var THIS = this,
			skuGroup = options.skuGroup,
			prodId = options.prodId,
			selectedOptions = options.selectedOptions,
			updateQty = (options.updateQty != undefined) ? options.updateQty:false,
			autoSelect = (options.autoSelect != undefined) ? options.autoSelect:false,
			triggerOptionType = (options.triggerOptionType != undefined) ? options.triggerOptionType:false,
			availableOptions = THIS.model.getFilteredOptions(prodId, selectedOptions),
			selectedValue = '',
			optionType = '',
			optionId = '',
			skuSelected = true,
			isDisabled = false,
			disabledValue,
			enabledItems;

		skuGroup.$selectors.each(function (i) {
			optionType = $(this).attr('data-attribute');
			selectedValue = $(this).val();
			enabledItems = 0;
			$('option', this).each(function () {
				
				optionId = $(this).attr('value');
				isDisabled = (optionId != '-1' && (availableOptions[optionType] === undefined || availableOptions[optionType][optionId] === undefined)) ? true:false;
				disabledValue = (isDisabled) ? 'disabled':'';
				if (isDisabled) {
					
					$(this).css('color', '#ccc').attr('disabled', 'disabled');
				} else {
					$(this).css('color', '#000').removeAttr('disabled');
				}
				if (!isDisabled) {
					enabledItems++;
				}
			});
			if (enabledItems == 2 && selectedOptions[optionType] == undefined && autoSelect == true) {
				for (i in availableOptions[optionType]) {
					selectedOptions[optionType] = availableOptions[optionType][i].optionId;
				}
				THIS.updateSelectors({'skuGroup':skuGroup, 'prodId':prodId, 'selectedOptions':selectedOptions, 'updateQty':updateQty});
				return;
			}
			if (selectedOptions[optionType] === undefined) {
				selectedValue = '-1';
				skuSelected = false;
			} else {
				selectedValue = selectedOptions[optionType];
			}
			$(this).val(selectedValue);
		});
		if (skuSelected !== false) {
			skuGroup.catalogRefId = THIS.model.getFilteredSkus(prodId, selectedOptions)[0].catalogRefId;
			this.updateAvailability(skuGroup, updateQty);
		} else {
			skuGroup.catalogRefId = '';
			this.resetAvailablility(skuGroup, updateQty);
		}
		this.colorizeSkuGroupImage(skuGroup, prodId, selectedOptions, skuSelected, triggerOptionType);
	},
	/**
	 * If the skuGroup has an image, get the colorized url and call colorizeImage.
	 * 
	 * @param {object} skuGroup The skuGroup object this change applies to.
	 * @param {string} prodId The id of this product.
	 * @param {object} selectedOptions The selected options as key value pairs, where the key is the optionType.
	 * @param {boolean} skuSelected Indicates that a sku has been selected
	 * @param {string} [triggerOptionType] Which optionType triggered the update
	 * @see ProductController.getColorData
	 * @see ProductController.getColorizedUrl
	 * @see ProductController.colorizeImage
	 */
	colorizeSkuGroupImage : function (skuGroup, prodId, selectedOptions, skuSelected, triggerOptionType) {
		if (skuGroup.$image.length > 0) {
			var colorizeType = this.model.getColorizeType(prodId),
				colorizeData = this.getColorData(skuGroup, selectedOptions, skuSelected, triggerOptionType),
				colorizedUrl = this.getColorizedUrl(skuGroup, colorizeType, colorizeData);
			if (colorizedUrl != '') {
				skuGroup.photoController.colorizeImage(colorizedUrl, colorizeData[0].id);
			}
		}
	},
	/**
	 * Gets the colorization swatch data. If a sku is selected, use it. Otherwise use the first matching sku.
	 * Code has primary and secondary swatch options in order to support multiple colorizations in the future. 
	 * Though currently, we only use the primary. These are arrays of the option types that the swatch represents.
	 * If the colorization is triggered by a select change (indicated by triggerOptionType) then check to see that the 
	 * triggering option type is one of the colorization options and if so do a colorization.
	 * 
	 * @param {object} skuGroup The skuGroup object this change applies to.
	 * @param {object} selectedOptions The selected options as key value pairs, where the key is the optionType.
	 * @param {boolean} skuSelected Indicates that a sku has been selected
	 * @param {string} [triggerOptionType] Which optionType triggered the update
	 * @returns {array} colors array of colorization objects.
	 */
	getColorData : function (skuGroup, selectedOptions, skuSelected, triggerOptionType) {
		var colors = [],
			prodId = skuGroup.prodId,
			primarySwatchOptions = this.model.getPrimarySwatchOptions(prodId),
			secondarySwatchOptions = this.model.getSecondarySwatchOptions(prodId),
			selectedSku,
			x,
			matchCount = 0;
		if (skuSelected === true) {
			selectedSku = this.model.getSku(prodId, skuGroup.catalogRefId);
		} else {
			selectedSku = this.model.getFilteredSkus(prodId, selectedOptions)[0];
		}
		if (selectedSku.colorization !== undefined && selectedSku.colorization.length > 0) {
			if (triggerOptionType) {
				if (primarySwatchOptions !== false) {
					matchCount = 0;
					for (x = 0; x < primarySwatchOptions.length; x++) {
						for (option in selectedOptions) {
							if (option == primarySwatchOptions[x]) {
								matchCount++;
								break;
							}
						}
					}
					if (matchCount == primarySwatchOptions.length) {
						colors.push(selectedSku.colorization[0]);
					}
				}
				/* TODO: finish multi colorization */
				if (secondarySwatchOptions !== false) {
					matchCount = 0;
					for (x = 0; x < secondarySwatchOptions.length; x++) {
						for (option in selectedOptions) {
							if (option == secondarySwatchOptions[x]) {
								matchCount++;
								break;
							}
						}
					}
					if (matchCount == secondarySwatchOptions.length) {
						// colors.push(selectedSku.colorization[1]);
					}
				}
			} else {
				// for (x = 0; x < selectedSku.colorization.length; x++) {
				// colors.push(selectedSku.colorization[x]);
				// }
				colors.push(selectedSku.colorization[0]);
			}
		}
		return colors;
	},
	/**
	 * Generates the colorized image url based off of the colorization type.
	 * 
	 * @param {object} skuGroup The skuGroup object this change applies to.
	 * @param {string} colorizeType Which method to use for colorization, 'static-color', 'dynamic-color' or 'dynamic-material'
	 * @param {object} colorizeData Indicates that a sku has been selected
	 * @returns {string} colorizedUrl url src for the colorizaed image
	 */
	getColorizedUrl : function (skuGroup, colorizeType, colorizeData) {
		var colorizedUrl = '',
			baseUrl = skuGroup.origImage.substring(0, skuGroup.origImage.indexOf('.com/') + 5),
			recipe = skuGroup.origImage.substring(skuGroup.origImage.indexOf('$'), skuGroup.origImage.length),
			res;
		if (colorizeData.length == 1) {
			if (colorizeType == 'static-color' && colorizeData[0].id != undefined) {
				colorizedUrl = baseUrl + 'is/image/' + s7data.staticHost + '/' + skuGroup.imgBase + '_cl'+ colorizeData[0].id + '?' + recipe;
			} else if (colorizeType == 'dynamic-color' && colorizeData[0].color != undefined) {
				colorizedUrl = baseUrl + 'is/image/' + s7data.staticHost + '?src=ir{'+ s7data.dynamicHost + '/' + skuGroup.imgBase + '?color=' + colorizeData[0].color + '}&' + recipe;
			} else if (colorizeType == 'dynamic-material' && colorizeData[0].color != undefined) {
				res = (colorizeData[0].materialRes !== undefined) ? colorizeData[0].materialRes:'';
				colorizedUrl = baseUrl + 'is/image/' + s7data.staticHost + '?src=ir{'+ s7data.dynamicHost + '/' + skuGroup.imgBase + '?src=' + s7data.dynamicHost + '/' + colorizeData[0].color + '&res=' + res + '}&' + recipe;
			}
		} else if (colorizeData.length == 2) {
			if (colorizeType == 'static-color' && colorizeData[0].id != undefined) {
				colorizedUrl = baseUrl + 'is/image/' + s7data.staticHost + '/' + skuGroup.imgBase + '_cl' + colorizeData[0].id + '_cl'+ colorizeData[1].id + '?' + recipe;
			}
		}
		return colorizedUrl;
	},
	/**
	 * Clears out form values and sets qtys to 0. Used to reset the form after submission.
	 */
	resetProductForm : function () {
		for (var i = 0; i < this.skuGroups.length; i++) {
			this.skuGroups[i].$qtyInput.val('0');
		}
		this.$formContainer.empty();
	},
	/**
	 * Once a sku has been selected, query the price, availability and delivery data and display the information. This data is 
	 * stored in an object (in the ProductModel) so that the ajax query only has to run once. Somtimes the postal code is required 
	 * in order to get the item's availability or to provide an accurate shipping estimate. For the availability, the postal code 
	 * is required before you can add the item to your cart. The delivery estimate postal code is not required for adding the item 
	 * to cart. In cases where the delivery and availability are postal code specific, we don't want to have 2 postal code inputs. 
	 * So we prompt for the postal code in the availability area. If we have provided availability or deliery estimate from a cookied 
	 * postal code, we provide the user with the ability to change the postal code.
	 * 
	 * TODO: move some of the static, repeated messages into parameters.
	 * 
	 * @param {object} skuGroup The skuGroup object this change applies to.
	 * @param {boolean} updateQty Flag to automatically change the qty from 0 to 1
	 * 
	 */
	updateAvailability : function (skuGroup, updateQty) {
		var THIS = this,
			catalogRefId = skuGroup.catalogRefId,
			availabilityMsg = THIS.model.getSkuAvailability(catalogRefId),
			itemDetailMessage = '',
			postalCodeMessage = '',
			deliveryMessage = '',
			inventoryMessage = '',
			finalSaleMessage = '',
			termsOfSaleMessage = '',
			price = '',
			postalForm = this.postalCodeInputs;
		/* The availability has been reset (zip code change), requery. */
		if (availabilityMsg === false) {
			THIS.model.setSkuAvailability(catalogRefId, THIS, skuGroup, updateQty);
			return;
		}
		/* Sku is not web purchasable, show error message and return. */
		if (availabilityMsg.inventoryCode == '-2') {
			this.showPurchasableError(skuGroup);
			return;
		}
		/* Missing data. show error message and return. */
		if (availabilityMsg === undefined || availabilityMsg.inventoryCode === undefined || availabilityMsg.listPrice === undefined || availabilityMsg.listPrice == '') {
			this.showInventoryError(skuGroup);
			return;
		}
		/* Availability (postalcodeSpecific) or delivery needs postal code. */
		if ((availabilityMsg.postalCodeSpecific == 'true' || availabilityMsg.deliveryCode == '100' || availabilityMsg.deliveryCode == '101' || availabilityMsg.deliveryCode == '102') && availabilityMsg.deliveryPostalCode != undefined) {
			postalForm = '<input type="text" id="postal-code" class="postal-code" size="6" maxlength="6" autocomplete="off" tabindex="0" value="' + availabilityMsg.deliveryPostalCode + '"/><input type="submit" value="Enter" class="submit postal-trigger"/>';
		}
		/* Item is not out of stock. */
		if (availabilityMsg.inventoryCode != '1001') {
			/* generate sku price message */
			if (availabilityMsg.saleFlag == 'true') {
				if (availabilityMsg.clearanceFlag == 'true') {
					price = '<div class="label">Price</div><div class="price"><strike>' + availabilityMsg.listPrice + ' ea</strike> <strong class="sale">Final Sale ' + availabilityMsg.salePrice + ' ea</strong></div>';
				} else {
					price = '<div class="label">Price</div><div class="price"><strike>' + availabilityMsg.listPrice + ' ea</strike> <strong class="sale">Special ' + availabilityMsg.salePrice + ' ea</strong></div>';
				}
			} else {
				price = '<div class="label">Price</div><div class="price">' + availabilityMsg.listPrice + ' ea</div>';
			}
			/* Shipping surcharge message */
			if (availabilityMsg.shippingFlag == 'true') {
				if (availabilityMsg.shippingPrice !== undefined) {
					price += '<p class="surcharge">+ Shipping Surcharge ' + availabilityMsg.shippingPrice + ' ea</p>';
				}
			}
			/* Terms (final sale and canada shippable) */
			if (availabilityMsg.clearanceFlag == 'true') {
				if (availabilityMsg.isCanadaShippable == 'false') {
				finalSaleMessage = '<div class="item-detail"><div class="label">Terms of Sale</div><p><span class="sale">This item is final sale and cannot be returned.</span><br/>This item cannot be shipped to Canada</p></div>';
				} else {
					finalSaleMessage = '<div class="item-detail"><div class="label">Terms of Sale</div><p class="sale">This item is final sale and cannot be returned.</p></div>';
				}
			} else {
				if (availabilityMsg.isCanadaShippable == 'false') {	
					termsOfSaleMessage = '<div class="item-detail"><div class="label">Terms of Sale</div><p>This item cannot be shipped to Canada</p></div>';
				}
			}
			/* Append price to skuGroup */
			skuGroup.$price.html(price);
		}
		/* TODO: redundant if condition. */
		if (availabilityMsg.inventoryCode != '1001') {
			skuGroup.$qtyInput.removeAttr('disabled');
		}
		/* Items availability is based on postal code*/
		if (availabilityMsg.postalCodeSpecific == 'true') {
			/* Missing the postal code */
			if (availabilityMsg.inventoryCode == '9999' || availabilityMsg.deliveryCode == '101') {
				/* Standard Delivery */
				if (availabilityMsg.deliveryCode == '200') {
					if (availabilityMsg.isFreightExempt == 'true') {
						deliveryMessage += '<p>Ships free of charge via ' + this.standardDeliveryLink + '<br/>';
					} else {
						deliveryMessage += '<p>' + this.standardDeliveryLink + '<br/>';
					}
					
					if (availabilityMsg.deliveryMessage != '') {
						deliveryMessage += availabilityMsg.deliveryMessage;
					}
					deliveryMessage += '</p>';
				/* Prompt for zip for delivery estimate (not required). Form is below in availability message. */
				} else {
					deliveryMessage += '<p>' + this.furnitureDeliveryLink + ' starting at ' + availabilityMsg.deliveryEstimate + '. Enter your Zip/Postal code for an estimate.';
					if (availabilityMsg.deliveryMessage != '') {
						deliveryMessage += '<br/>' + availabilityMsg.deliveryMessage;
					}
					deliveryMessage += '</p>';
				}
				/* Zip is required for the availability. Cannot be added to cart unless zip is provided. */
				inventoryMessage += '<fieldset><span class="error">Zip/Postal code is required to add this item to your cart.</span><br/>'
				inventoryMessage += postalForm + '</fieldset>';
				skuGroup.$qtyInput.removeAttr('disabled').val('0');
			/* Postal code is present, but invalid. Prompt for an update */
			} else if (availabilityMsg.deliveryCode == '102') {
				deliveryMessage += '<p>' + this.furnitureDeliveryLink + ' starting at ' + availabilityMsg.deliveryEstimate + '. Enter your Zip/Postal code for an estimate.';
				if (availabilityMsg.deliveryMessage != '') {
					deliveryMessage += '<br/>' + availabilityMsg.deliveryMessage;
				}
				deliveryMessage += '</p>';
				inventoryMessage += '<fieldset><span class="error">A valid US or Canadian Zip/Postal code is required to add this item to your cart. </span><br/>';
				inventoryMessage += postalForm + '</fieldset>';
				skuGroup.$qtyInput.val('0');
			/* Item is out of stock for this postal code. */
			} else if (availabilityMsg.inventoryCode == '1001') {
				if (availabilityMsg.inventoryMessage != '') {
					inventoryMessage += '<p><span class="postalcode-update">' + availabilityMsg.inventoryMessage + ' for Zip/Postal code ' + availabilityMsg.deliveryPostalCode  + ' (<a class="postalform-trigger-inventory">change location</a>). </span>';
					inventoryMessage += '<span class="postalcodeform-inline" style="display:none;">For Zip/Postal Code ' + postalForm + '</span>';
					inventoryMessage += '</p>';
				}
				skuGroup.$qtyInput.attr('disabled','disabled').val('0');
			/* Postal code is required, and we have it from the postal code cookie. */
			} else {
				/* We have a delivery estimate */
				if (availabilityMsg.deliveryCode == '100') {
					deliveryMessage += '<p>' + this.furnitureDeliveryLink;
					deliveryMessage += '<span class="postalcode-update"> for ' + availabilityMsg.deliveryEstimate + '</span> to Zip/Postal code <span class="postalcode-update">' + availabilityMsg.deliveryPostalCode + ' (<a class="postalform-trigger-delivery">change location</a>). </span>';
					deliveryMessage += '<span class="postalcodeform-inline" style="display:none;">' + postalForm + '</span>';
					if (availabilityMsg.deliveryMessage != '') {
						deliveryMessage += '<br/>' + availabilityMsg.deliveryMessage;
					}
					deliveryMessage += '</p>';
				/* Standard Delivery */
				} else if (availabilityMsg.deliveryCode == '200') {
					if (availabilityMsg.isFreightExempt == 'true') {
						deliveryMessage += '<p>Ships free of charge via ' + this.standardDeliveryLink + '<br/>';
					} else {
						deliveryMessage += '<p>' + this.standardDeliveryLink + '<br/>';
					}
					if (availabilityMsg.deliveryMessage != '') {
						deliveryMessage += availabilityMsg.deliveryMessage;
					}
					deliveryMessage += '</p>';
				}
				/* Indicate the Availability is based off the stored postal code. */
				if (availabilityMsg.inventoryMessage != '') {
					inventoryMessage += '<p><span class="postalcode-update">' + availabilityMsg.inventoryMessage + ' for Zip/Postal code ' + availabilityMsg.deliveryPostalCode  + ' (<a class="postalform-trigger-inventory">change location</a>). </span>';
					inventoryMessage += '<span class="postalcodeform-inline" style="display:none;">For Zip/Postal Code ' + postalForm + '</span>';
					inventoryMessage += '</p>';
				}
				/* Update the qty to 1. (always do this for quick view) */
				if (updateQty || quickView.getStatus()) {
					skuGroup.$qtyInput.val('1');
				}
			}
		/* Postal code is not required for availability. */
		} else {
			/* Item is in stock. */
			if (availabilityMsg.inventoryCode != '1001') {
				/* Missing postal code for estimate prompt for this, but it's not required. */
				if (availabilityMsg.deliveryCode == '101') {
					deliveryMessage += '<p>' + this.furnitureDeliveryLink + ' starting at ' + availabilityMsg.deliveryEstimate + '. Enter your Zip/Postal code for an estimate.</p>';
					deliveryMessage += '<fieldset>' + postalForm + '</fieldset>';
					if (availabilityMsg.deliveryMessage != '') {
						deliveryMessage += '<p>' + availabilityMsg.deliveryMessage + '</p>';
					}
				/* Pastal code is invalid */
				} else if (availabilityMsg.deliveryCode == '102') {
					deliveryMessage += '<p>' + this.furnitureDeliveryLink + ' starting at ' + availabilityMsg.deliveryEstimate + '.</p>';
					deliveryMessage += '<fieldset><span class="error">Please enter a valid US or Canadian Zip/Postal code.</span><br/>';
					deliveryMessage += postalForm + '</fieldset>';
				/* Estimate is based off the stored postal code cookie. */
				} else if (availabilityMsg.deliveryCode == '100') {
					deliveryMessage += '<p>' + this.furnitureDeliveryLink;
					deliveryMessage += '<span class="postalcode-update"> for ' + availabilityMsg.deliveryEstimate + '</span> to Zip/Postal code <span class="postalcode-update">' + availabilityMsg.deliveryPostalCode + ' (<a class="postalform-trigger-delivery">change location</a>). </span>';
					deliveryMessage += '<span class="postalcodeform-inline" style="display:none;">' + postalForm + '</span>';
					if (availabilityMsg.deliveryMessage != '') {
						deliveryMessage += '<br/>' + availabilityMsg.deliveryMessage;
					}
					deliveryMessage += '</p>';
				/* Standard Delivery */
				} else if (availabilityMsg.deliveryCode == '200') {
					if (availabilityMsg.isFreightExempt == 'true') {
						deliveryMessage += '<p>Ships free of charge via ' + this.standardDeliveryLink + '<br/>';
					} else {
						deliveryMessage += '<p>' + this.standardDeliveryLink + '<br/>';
					}
					if (availabilityMsg.deliveryMessage != '') {
						deliveryMessage += availabilityMsg.deliveryMessage;
					}
					deliveryMessage += '</p>';
				}
				if (availabilityMsg.inventoryMessage != '') {
					inventoryMessage +=  availabilityMsg.inventoryMessage + '.';
				}
				/* Update the qty to 1. (always do this for quick view) */
				if (updateQty || quickView.getStatus()) {
					skuGroup.$qtyInput.val('1');
				}
			/* Item is out of stock. */
			} else {
				if (availabilityMsg.inventoryMessage != '') {
					inventoryMessage +=  availabilityMsg.inventoryMessage + '.';
				}
				skuGroup.$qtyInput.attr('disabled', 'disabled').val('0');
			}
		}
		/* Assemble the item detail message (inventory, delivery, terms) */
		itemDetailMessage += '<div class="item-detail">';
		if (inventoryMessage !='') {
			itemDetailMessage += '<div class="item-inventory-message"><div class="label">Availability</div>' + inventoryMessage + '</div>';
		}
		if (deliveryMessage !='') {
			itemDetailMessage += '<div class="item-delivery-message"><div class="label">Delivery</div>' + deliveryMessage + '</div>';
		}
		itemDetailMessage += '</div>'
		itemDetailMessage += finalSaleMessage;
		itemDetailMessage += termsOfSaleMessage;
		/* Display sku Id */
		skuGroup.$sku.html('<span class="label">Item#</span>' + availabilityMsg.fullSkuId);
		/* Display item detail message */
		skuGroup.$details.html(itemDetailMessage);
	},
	/**
	 * Clear sku information froma skuGroup.
	 * @param {object} skuGroup The skuGroup object this change applies to.
	 */
	resetAvailablility : function (skuGroup) {
		skuGroup.$qtyInput.attr('disabled', 'true');
		skuGroup.$sku.html('');
		skuGroup.$price.html('<div class="label">Price</div>');
		skuGroup.$details.html('');
		$('input.postal-code', skuGroup.$container).val('');
		$('.postalcodeform-inline', skuGroup.$container).hide();
		skuGroup.$qtyInput.val('0');
	},
	/**
	 * Check to see if the zip is required for any selected skus.
	 * @returns {boolean}
	 */
	requireZip : function () {
		var catalogRefId,
			availabilityMsg;
		for (var i = 0; i < this.skuGroups.length; i++) {
			catalogRefId = this.skuGroups[i].catalogRefId;
			availabilityMsg = this.model.getSkuAvailability(catalogRefId);
			if (this.skuGroups[i].enabled == true && (availabilityMsg !== false && availabilityMsg.inventoryCode == '9999')) {
				return true;
			}
		}
		return false;
	},
	/**
	 * Check to see if any of the selected skus are out of stock.
	 * @returns {boolean}
	 */
	noInventory : function () {
		var catalogRefId,
			availabilityMsg;
		for (var i = 0; i < this.skuGroups.length; i++) {
			catalogRefId = this.skuGroups[i].catalogRefId;
			availabilityMsg = this.model.getSkuAvailability(catalogRefId);
			if (this.skuGroups[i].enabled == true && (availabilityMsg !== false && availabilityMsg.inventoryCode == '1001')) {
				return true;
			}
		}
		return false;
	},
	/**
	 * This gets the sku data that will be used to submit the form. It can either return a single sku's data (by index) or 
	 * all the selected skus on the page.
	 * 
	 * TODO: Getting the sku data for a single selected product was built to support add-to-cart on each line item. But that interface has not been implemented.
	 * TODO: Monogramming needs to be upgraded so that there's not a limit on the number of lines (instead of being discretly named monogram fields, use an array)
	 * 
	 * @param {int} [index] which skuGroup's sku to retrieve. If empty gets them all.
	 * @returns {array} an array of sku data that will populate the forms.
	 */
	getSelectedSkus : function (index) {
		var skuArray = [],
			skuData = {},
			quantity = 0,
			prodId = '',
			catalogRefId = '',
			needsZip = this.requireZip(),
			hasNoInventory = this.noInventory(),
			x = 0,
			loopLength = this.skuGroups.length;
		if (index !== undefined) {
			x = index;
			loopLength = (x+1 < this.skuGroups.length) ? x+1 : this.skuGroups.length;
		}
		for (x; x < loopLength; x++) {
			skuGroup = this.skuGroups[x];
			if (skuGroup.enabled === false) {
				continue;
			}
			quantity = skuGroup.$qtyInput.val();
			if (quantity > 0) {
				prodId = skuGroup.prodId;
				catalogRefId = skuGroup.catalogRefId;
				if (!catalogRefId) {
					this.showError(errorMessages.missingDropdowns);
					return false;
				}
				var inventoryCode = this.model.getSkuAvailability(catalogRefId).inventoryCode;
				if (inventoryCode == '9999') {
					this.showError(errorMessages.zipRequired);
					return false;
				}
				/* Get basics */
				skuData.prodId = prodId;
				skuData.catalogRefId = catalogRefId;
				skuData.quantity = quantity;
				/* Get personalization data */
				if (skuGroup.$personalizeInput.length > 0) {
					if (skuGroup.$personalizeInput.is(':checked')) {
						monogramLayout = skuGroup.$container.find('select[name=prod' + x + '-fontAttributeId]');
						monogramColor = skuGroup.$container.find('select[name=prod' + x + '-threadColorAttrId]');
						monogramLine1 = skuGroup.$container.find('input[name=prod' + x + '-textLine1]');
						monogramLine2 = skuGroup.$container.find('input[name=prod' + x + '-textLine2]');
						monogramLine3 = skuGroup.$container.find('input[name=prod' + x + '-textLine3]');			
						monogramLayoutValue = monogramLayout.val();
						monogramColorValue = monogramColor.val();
						monogramLine1Value = monogramLine1.val();
						monogramLine2Value = monogramLine2.val();
						monogramLine3Value = monogramLine3.val();

						if (monogramLayout.length > 0 && monogramLayoutValue == '') {
							this.showError(errorMessages.missingMonogramStyle);
							return false;
						}
						if (monogramColor.length > 0 && monogramColorValue == '') {
							this.showError(errorMessages.missingMonogramColor);
							return false;
						}
						if (monogramLine1Value == '') {
							this.showError(errorMessages.missingMonogramText);
							return false;
						}
						skuData.personalize = 'true';
						skuData.monogramLine1 = monogramLine1Value;
						if (monogramLayout.length > 0) {
							skuData.monogramLayout = monogramLayoutValue;
						}
						if (monogramColor.length > 0) {
							skuData.monogramColor = monogramColorValue;
						}
						if (monogramLine2.attr('disabled') != true) {
							skuData.monogramLine2 = monogramLine2Value;
						}
						if (monogramLine3.attr('disabled') != true) {
							skuData.monogramLine3 = monogramLine3Value;
						}
					}
				}
				/* Get gift cert data */
				if ($('#gift-from', skuGroup.$container).length > 0) {
					toValue = $('#gift-to', skuGroup.$container).val();
					fromValue = $('#gift-from', skuGroup.$container).val();
					if (fromValue == '' && toValue == '') {
						this.showError(errorMessages.missingToAndFrom);
						return false;
					} else if (fromValue == '') {
						this.showError(errorMessages.missingFrom);
						return false;
					} else if (toValue == '') {
						this.showError(errorMessages.missingTo);
						return false;
					}
					skuData.to = toValue;
					skuData.from = fromValue;
				}
				skuArray.push(skuData);
				skuData = {}; // reset
			}
		}
		if (skuArray.length == 0) {
			/* error messaging */
			if (needsZip == true) {
				this.showError(errorMessages.missingQuantityPostal);
			} else if (hasNoInventory == true) {
				this.showError(errorMessages.missingQuantityInventory);
			} else {
				this.showError(errorMessages.missingQuantity);
			}
			return false;
		} else {
			return skuArray;
		}
	},
	/**
	 * This populates the form fields for the add to cart form
	 * 
	 * TODO: consolodate this and the addToGiftListUpdate into one function. differentiate using a config value.
	 * 
	 * @param {object} $form jquery form object
	 * @param {object} options Configuration object
	 * @config {array} skuArray array of sku data objects
	 */
	addToCartUpdate : function ($form, options) {
		var skuArray = options.skuArray,
			x = 0,
			form = $form[0];
		for (x=0; x < skuArray.length; x++) {
			skuData = skuArray[x];
			form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].catalogRefId'].value = skuData.catalogRefId;
			form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].productId'].value = skuData.prodId;
			form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].categoryId'].value = categoryId;
			form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].quantity'].value = skuData.quantity;
		// Monogram Fields
			if (skuData.personalize !== undefined) {
				form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].personalize'].value = skuData.personalize;
				form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].monogramTextLines[0]'].value = skuData.monogramLine1;
				if (skuData.monogramLayout !== undefined) {
					$(form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].monogramLayoutId']).removeAttr('disabled').val(skuData.monogramLayout);
				} else {
					$(form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].monogramLayoutId']).attr('disabled', 'disabled');
				}
				if (skuData.monogramColor !== undefined) {
					$(form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].monogramColorId']).removeAttr('disabled').val(skuData.monogramColor);
				} else {
					$(form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].monogramColorId']).attr('disabled', 'disabled');
				}
				if (skuData.monogramLine2 !== undefined) {
					$(form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].monogramTextLines[1]']).removeAttr('disabled').val(skuData.monogramLine2);
				} else {
					$(form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].monogramTextLines[1]']).attr('disabled', 'disabled');
				}
				if (skuData.monogramLine3 !== undefined) {
					$(form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].monogramTextLines[2]']).removeAttr('disabled').val(skuData.monogramLine3);
				} else {
					$(form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].monogramTextLines[2]']).attr('disabled', 'disabled');
				}
			} else {
				form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].personalize'].value = 'false';
			}
			
			if (skuData.to !== undefined) {
				form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].value.toField'].value = skuData.to;
				form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].value.fromField'].value = skuData.from;
			} else {
				$(form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].value.toField']).attr('disabled', 'disabled');
				$(form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].value.fromField']).attr('disabled', 'disabled');
			}
		}
		return true;
	},
	/**
	 * This populates the form fields for the add to gift list form. Like the add to cart, but the to and from fields for the gift cart are not used.
	 * 
	 * @param {object} $form jquery form object
	 * @param {object} options Configuration object
	 * @config {array} skuArray array of sku data objects
	 */
	addToGiftListUpdate : function ($form, options) {
		var skuArray = options.skuArray,
			x = 0,
			form = $form[0];
		for (x=0; x < skuArray.length; x++) {
			skuData = skuArray[x];
			form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].catalogRefId'].value = skuData.catalogRefId;
			form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].productId'].value = skuData.prodId;
			form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].quantity'].value = skuData.quantity;
		// Monogram Fields
			if (skuData.personalize !== undefined) {
				form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].personalize'].value = skuData.personalize;
				form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].monogramTextLines[0]'].value = skuData.monogramLine1;
				if (skuData.monogramLayout !== undefined) {
					$(form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].monogramLayoutId']).removeAttr('disabled').val(skuData.monogramLayout);
				} else {
					$(form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].monogramLayoutId']).attr('disabled', 'disabled');
				}
				if (skuData.monogramColor !== undefined) {
					$(form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].monogramColorId']).removeAttr('disabled').val(skuData.monogramColor);
				} else {
					$(form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].monogramColorId']).attr('disabled', 'disabled');
				}
				if (skuData.monogramLine2 !== undefined) {
					$(form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].monogramTextLines[1]']).removeAttr('disabled').val(skuData.monogramLine2);
				} else {
					$(form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].monogramTextLines[1]']).attr('disabled', 'disabled');
				}
				if (skuData.monogramLine3 !== undefined) {
					$(form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].monogramTextLines[2]']).removeAttr('disabled').val(skuData.monogramLine3);
				} else {
					$(form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].monogramTextLines[2]']).attr('disabled', 'disabled');
				}
			} else {
				form['/atg/commerce/order/purchase/CartModifierFormHandler.items[' + x + '].personalize'].value = 'false';
			}
		}
		return true;
	},
	/**
	 * Callback when the form submission is successful.
	 * 
	 * @param {string} responseText 
	 * @param {string} statusText indicates if the request succeded or failed.
	 * @param {object} xhr 
	 * @param {object} $form 
	 */
	handleResponse : function (responseText, statusText, xhr, $form) {
		var THIS = this.productGroup,
			jsonResponse = '',
			errorMessage = '';
		if (statusText == 'success') {
			/* Check if the response is json or not. a json response is expected from the postal form.*/
			try {
				jsonResponse = $.parseJSON(responseText);
			} catch (ex) {
				jsonResponse = false;
			}
			/* if this is not a json response, this is an add to cart/giftlist response. */
			if (!jsonResponse) {
				/* This is an html response, but make sure we didn't get a session error page. */
				if (responseText.indexOf('mod-order-items') > 0) {
					var originPage = String (document.location).split ('?')[0];
					if (originPage.indexOf("cart.jsp") > 0) {
							document.location.reload(true);
						return;
					}
					
					/* Show the success overlay. */
					THIS.resetProductForm();
					THIS.$errorContainer.empty();
					if (quickView.getStatus() === true) {
						removeOverlay();
						quickView.container.show({'content': responseText});
					} else {
						dialog.show({'content': responseText});
					}
				} else {
					/* Show the session error page */
					window.location.replace('/error/session.jsp');
				}
			/* if this is a json response, this is the postal code form */
			} else {
				/* make sure this was a sucessful postal code update */
				if (jsonResponse.type !== undefined && jsonResponse.type == 'postal' && jsonResponse.success == 'true') {
					/* reset any previously stored availability data */
					THIS.model.resetSkuAvailability();
					for (x=0; x < THIS.skuGroups.length; x++) {
						if (THIS.skuGroups[x].catalogRefId != '') {
							THIS.updateAvailability(THIS.skuGroups[x], false);
							$('input.postal-code', THIS.skuGroups[x].$container).val('');
							$('.postalcodeform-inline', THIS.skuGroups[x].$container).hide();
						}
					}
					THIS.$errorContainer.empty();
					$('#postalSubmit', THIS.$formContainer).remove();
					miniCart.mcClear();
					
					/* get rid of the loading screen*/
					if (quickView.getStatus() !== true) {
						removeOverlay();
					} else {
						quickView.container.hideLoader();
					}
				} else {
					/* there was an error. */
					if (jsonResponse.errors.length != 0) {
						for (var x = 0; x < jsonResponse.errors.length; x++) {
							errorMessage +=  jsonResponse.errors[x] ;
						}
					} else {
						errorMessage = THIS.communicationFailure;
					}
					/* get rid of the loading screen and show an error message. */
					if (quickView.getStatus() !== true) {
						removeOverlay();
						THIS.showError(errorMessage);
					} else {
						quickView.container.hideLoader();
						THIS.showError(errorMessage);
					}
				}
			}
		} else { 
			/* get rid of the loading screen and show an error message. 
			 * TODO: how come the quickview check is not here?
			 */
			removeOverlay();
			THIS.showError();
		}
	},
	/**
	 * Adds the error message to the top of the line list.
	 * 
	 * @param {string} [msg] The error message to display. If empty show the general communication failure message.
	 */
	showError: function (msg) {
		var errorMessage = '<div class="mod mod-error-msg group group-notflush-top"><h3 class="brand">Error<\/h3><div class="error-msg-block"><div class="error-msg">';
		errorMessage += (msg) ? msg:this.communicationFailure;
		errorMessage += '<\/div><\/div><\/div>';
		this.$errorContainer.html(errorMessage);
		if (quickView.getStatus() !== true) {
			window.scrollTo(0, this.$errorContainer.offset().top - 10);
		}
	},
	/**
	 * Shows an error message on a skuGroup for when the inventory lookup fails.
	 * @param {object} skuGroup The skuGroup object this change applies to.
	 */
	showInventoryError : function (skuGroup) {
		var errorMessage = '<div class="item-detail error"><div class="label">Details</div>' + ajaxError + '</div>';
		skuGroup.$qtyInput.attr('disabled', 'true');
		skuGroup.$sku.html('');
		skuGroup.$price.html('<div class="label">Price</div>');
		skuGroup.$details.html(errorMessage);
	},
	/**
	 * Shows an out of stock message (call customer service) on the skuGroup
	 * @param {object} skuGroup The skuGroup object this change applies to.
	 */
	showPurchasableError : function (skuGroup) {
		var errorMessage = '<div class="item-detail error"><div class="label">Details</div>' + errorMessages.purchasableError + '</div>';
		skuGroup.$qtyInput.attr('disabled', 'true');
		skuGroup.$sku.html('');
		skuGroup.$price.html('<div class="label">Price</div>');
		skuGroup.$details.html(errorMessage);
	},
	/**
	 * The hover behavior for the swatches. Shows a popup with a larger swatch photo. Optionally 
	 * expands this box with swatch fabric details.Has logic to determine if the box should popup 
	 * towards the right or the left.
	 * @param {object} $trigger The event trigger, jquery object.
	 */
	swatchOver : function ($trigger) {
		$('.swatch-details').remove();
		var swatchId = $trigger.attr('data-id'),
			swatchData = this.swatchModel.get(swatchId),
			THIS = this,
			colorize = (swatchData.colorize !== undefined) ? swatchData.colorize:'',
			details = (swatchData.details !== undefined) ? swatchData.details:'',
			requestable = (swatchData.requestable !== undefined) ? swatchData.requestable:'',
			primaryOptionId = (swatchData.primaryOptionId !== undefined) ? swatchData.primaryOptionId:'',
			swatchPage = '/catalog/product/includes/swatch-details.jsp?selected=' + swatchId + '&colorize=' + colorize + '&requestable=' + requestable + "&primaryOptionId=" + primaryOptionId,
			$swatchOverlay = $('<div class="swatch-details"></div>'),
			overlayWidth = 313;
			$triggerParent = $trigger.parent(),
			triggerPosition = $trigger.offset(),
			swatchGroupMidpoint = THIS.$swatchContainer.offset().left + overlayWidth,
			pageMidpoint = $('#main-body').offset().left + $('#main-body').width() / 2,
			getImgParam = $trigger.attr('src'),
			alignLeft = false,
			expandLeft = false,
			thisWidth = $trigger.width(),
			thisHeight = $trigger.height();
		getImgParam = getImgParam.split('?$');
		getImgParam = getImgParam[1].split('$');
		
		if (THIS.$swatchContainer.offset().left > pageMidpoint) {
			expandLeft = true;
		} else {
			expandLeft = false;
		}
		if (triggerPosition.left < swatchGroupMidpoint) {
			alignLeft = true;
		} else {
			alignLeft = false;
		}
		if (alignLeft) {
			if (expandLeft) {
				$swatchOverlay.css({right: thisWidth - overlayWidth}).css({bottom: thisHeight + 9});
			} else {
				$swatchOverlay.css({left: 0}).css({bottom: thisHeight + 9});
			}
		} else {
			if (expandLeft) {
				$swatchOverlay.css({right: 0}).css({bottom: thisHeight + 9});
			} else {
				$swatchOverlay.css({left: thisWidth - overlayWidth}).css({bottom: thisHeight + 9});
			}
		}
		$swatchOverlay.appendTo($triggerParent).fadeIn(300);
		$swatchOverlay.load(swatchPage);
		if (details == 'true') {
			$swatchOverlay.oneTime(1600, 'growBox', function () {
				$(this).animate({
					width: '585px'
				}, 600);
				
			});
		}
	},
	/**
	 * Hide the swatch detail box.
	 * @param {object} $trigger The event trigger, jquery object.
	 */
	swatchOut : function ($trigger) {
		var $hoverBox = $trigger.parent().find('.swatch-details');
		$hoverBox.stopTime('growBox').fadeOut(300, function () {
			$hoverBox.remove();
		});
	},
	/**
	 * Highlights the selected swatch, colorize the main product photo. Select the swatch attributes on the page's products.
	 * @param {object} $trigger The event trigger, jquery object.
	 */
	swatchSelect : function ($trigger) {
		var swatchId = $trigger.attr('data-id'),
			swatchData = this.swatchModel.get(swatchId),
			attributes = (swatchData.attributes !== undefined) ? swatchData.attributes:'',
			colorize = (swatchData.colorize !== undefined) ? swatchData.colorize:'',
			imgUrl = (swatchData.imgurl !== undefined) ? swatchData.imgurl:'';
		if (colorize == 'true') {
			$('#alt-photo0').attr('src', imgUrl);
			this.photoController.switchImage(0);
			activeElement = $trigger.closest('li');
			selectItemByTrigger(activeElement, '#product-swatches div.swatch-group li');
			this.photoController.updateZoomLink(swatchId);
			selectItemByIndex(-1, '#product-photo-thumbs li');
		}
		this.selectSwatchAttributes(attributes); 
	},
	/**
	 * Used by the fall lighting template. Does a rough calculation of how many bullets we can fit 
	 * on the page before the left column goes longer than the product photo.
	 * @see collapsableList
	 */
	collapseList: function () {
		var bulletCount = 2, //the smallest bullet count
			textHeight = $('.product-text', this.$viewContainer).height(),
			mediaHeight = $('.product-media', this.$viewContainer).height(),
			listHeight = $('.product-list', this.$viewContainer).height(),
			photoHeight = $('#product-imagery', this.$viewContainer).height(),
			listItems = $('.product-list li', this.$viewContainer),
			maxTextHeight = photoHeight - mediaHeight,
			trimHeight = textHeight - maxTextHeight,
			count = 0,
			runningListHeight = 0,
			x;

		if (textHeight + mediaHeight > photoHeight) {
			// need to trim some bullets
			if (trimHeight < listHeight) {
				count = listItems.length;
				for (x = count; x >= 3; x--) {
					runningListHeight += listItems.eq(x).height();
					bulletCount = x - 1; // subtract one, because more link takes up room.
					if (runningListHeight > trimHeight) {
						break;
					}
				}
			} 
			$('ul.product-list', this.$viewContainer).collapsableList({'size':bulletCount, 'showLabel':'More', 'hideLabel':'Less'});
		}
	}
};

// DOM READY
$(document).ready(function () {
	// LINKS TO FAQ ITEMS ALWAYS EXPAND THAT ITEM
		$('a[href*="#faq"]').click(function () {
			var link = $(this).attr('href');
			var linkArr = link.split("#");
			var faqId = linkArr[1];
			openFaq("#" + faqId);
		});

	/* PRODUCT ZOOM */
	$('.zoom-trigger').live('mouseover click', function (event) {
		if (event.type == 'mouseover') {
			var elId = $('a.zoom-link', this).attr('id'),
				url = $('a.zoom-link', this).attr('href');
			if (!url) { return false; }
			productZoom.showLauncher({'elId':elId, 'url':url, el:this});
			
		} else {
			var url = $('a.zoom-link', this).attr('href');
			productZoom.launch({'url':url});
			return false;
		}
	});

	/* MODALS */
	dialog = new ModalWindow();
	$('.modal-trigger').live('click', function (event) {
		url = $(this).attr('href');
		if (!url) { return false; }
		if (!window['defaultModal']) {
			defaultModal = new ModalWindow();
		}
		defaultModal.show({'url':url});
		return false;
	});

	/* LEFT NAV */	
	var navPathName = location.pathname;
	if ( navPathName ) {
		$('#leftnav a[href="' + navPathName + '"]').attr('class', 'activated');
	}

	/* TOOL TIPS */
	$(':input.tipped').tooltip({
		// place tooltip on the right edge
		position: "center right",
		// a little tweaking of the position
		offset: [0, 5],
		// use the built-in fadeIn/fadeOut effect
		effect: "fade"
	});

	/* STRIPED TABLE CELLS AND DIVS */
	$('table.striped tbody tr:odd').addClass('even');
	$('div.striped:odd').addClass('even');

	/* SUCKERFISH FALLBACK FOR SIDEBAR FLYOUTS */
	$('#sidebar ul li').each(function (index) {
		$(this).hover(function () {
			$(this).addClass('sfhover');
		}, function () {
			$(this).removeClass('sfhover');
		});
	});

	/* FAQ ANSWER REVEAL */
	$('dl.faq dt span').click(function () {
		$(this).parent().next('dd').toggle();
	});

	/* FAQ ANSWER ANCHOR */
	if ($('dl.faq').length > 0) {
		anchorId = location.hash;
		$(anchorId).parent().next('dd').toggle();
	}

	/* CHECKOUT SHIP TO BLOCK */
	$('div.ship-address-block').click(function () {
		$(this).find('input').attr('checked', 'true');
	});

	/* PERSONALIZATION */
	$('.select-color').selectmenu({
		positionOptions: {offset: "0 -1"},
		maxHeight: 250,
		width: 220,
		icons: [{find: '.swatch-image'}],
		bgImage: function () {return 'url(' + $(this).attr("title") + ')';}
	});
	$('.select-layout').selectmenu({
		positionOptions: {offset: "0 -1"},
		maxHeight: 250,
		width: 220,
		icons: [{find: '.style-image'}],
		bgImage: function () {return 'url(' + $(this).attr("title") + ')';}
	});
	$('.item-personalize').click(function (e) {
		var $target = $(e.target);
		var $personalizationForm = $('.personalize-form', this);
		if ($target.hasClass('selected')) {
			$('.personalize-update', this).click();
		} else if ($target.hasClass('personalizeInput')) {
			$personalizationForm.toggle($target.is(':checked'));
		} else if ($target.hasClass('personalize-edit')) {
			$personalizationForm.show();
			$('.personalize-attributes', this).hide();
		} else if ($target.hasClass('personalize-cancel')) {
			$personalizationForm.hide();
			$('.personalize-attributes', this).show();
			return false;
		} else if ($target.hasClass('personalize-update')) {
			// cart submission
			var personalize = $('.personalizeInput', this).is(':checked'),
				itemId = $(this).attr('data-item'),
				productId = $(this).attr('data-product'),
				monogramLine1 = '',
				monogramLayout = '',
				monogramColor = '',
				monogramLine2 = '';
				monogramLine3 = '';

			$('#monogram-itemId').val(itemId);
			$('#monogram-productId').val(productId);
			$('#monogram-personalize').val(personalize);

			if (personalize == false) {
				return true;
			}

			var monogramLayout = $('.select-layout', this),
				monogramColor = $('.select-color', this),
				monogramLine1 = $('.line1-text', this),
				monogramLine2 = $('.line2-text', this),
				monogramLine3 = $('.line3-text', this),
				monogramLayoutValue = monogramLayout.val(),
				monogramColorValue = monogramColor.val(),
				monogramLine1Value = monogramLine1.val(),
				monogramLine2Value = monogramLine2.val(),
				monogramLine3Value = monogramLine3.val(),
				errorContainer = "";

			if (monogramLayout.length > 0 && monogramLayoutValue == '') {
				if ($('.error-msg', this).length > 0) {
					$('.error-msg', this).html(errorMessage);
				} else {
					errorContainer = '<div class="mod mod-error-msg group group-notflush-top"><h3 class="brand">Error<\/h3><div class="error-msg-block"><div class="error-msg">' + errorMessages.missingMonogramStyle + '<\/div><\/div><\/div>';
					$('.personalize-form', this).prepend(errorContainer);
				}
				return false;
			}
			if (monogramColor.length > 0 && monogramColorValue == '') {
				if ($('.error-msg', this).length > 0) {
					$('.error-msg', this).html(errorMessage);
				} else {
					errorContainer = '<div class="mod mod-error-msg group group-notflush-top"><h3 class="brand">Error<\/h3><div class="error-msg-block"><div class="error-msg">' + errorMessages.missingMonogramColor + '<\/div><\/div><\/div>';
					$('.personalize-form', this).prepend(errorContainer);
				}
				return false;
			}
			if (monogramLine1Value == '') {
				if ($('.error-msg', this).length > 0) {
					$('.error-msg', this).html(errorMessage);
				} else {
					errorContainer = '<div class="mod mod-error-msg group group-notflush-top"><h3 class="brand">Error<\/h3><div class="error-msg-block"><div class="error-msg">' + errorMessages.missingMonogramText + '<\/div><\/div><\/div>';
					$('.personalize-form', this).prepend(errorContainer);
				}
				return false;
			}

			$('#monogram-monogramTextLines0').val(monogramLine1Value);

			if (monogramLayout.length > 0) {
				$('#monogram-monogramLayoutId').removeAttr('disabled').val(monogramLayoutValue);
			} else {
				$('#monogram-monogramLayoutId').attr('disabled', 'disabled');
			}
			if (monogramColor.length > 0) {
				$('#monogram-monogramColorId').removeAttr('disabled').val(monogramColorValue);
			} else {
				$('#monogram-monogramColorId').attr('disabled', 'disabled');
			}
			if (monogramLine2.attr('disabled') != true) {
				$('#monogram-monogramTextLines1').removeAttr('disabled').val(monogramLine2Value);
			} else {
				$('#monogram-monogramTextLines1').attr('disabled', 'disabled');
			}
			if (monogramLine3.attr('disabled') != true) {
				$('#monogram-monogramTextLines2').removeAttr('disabled').val(monogramLine3Value);
			} else {
				$('#monogram-monogramTextLines2').attr('disabled', 'disabled');
			}
			return true;
		}
	}).change(function (e) {
		var $target = $(e.target);
		if ($target.hasClass('select-layout')) {
			selectScript(this, $target);
		}
	}).each(function () {
		var $target = $('.select-layout', this);
		selectScript(this, $target);
	});
	/* DOC WRITE FIX - for modal content that includes doc write */
	document.write = (function () {
		for (var i = 0; i < arguments.length; i++) {
			$('body').append(arguments[i]);
		}
	});
});


/**
 * This triggers the type ahead feature on search after a certain number of characters are typed.
 */
function typeAhead(query, typeAheadMinimumLength) {
	$(document).keyup(function (e) {
		if ($(query).val().length >= typeAheadMinimumLength && e.keyCode != 13) {
			$.ajax({
				url: '/search/typeahead-results.jsp?terms=' + $(query).val(), 
				type:'GET', 
				dataType: 'html', 
				success:function (data) {
					$('#search-typeahead2').html(data);
					$('.search-input').addClass('dropped');
				},
				error:function (XMLHttpRequest, textStatus, errorThrown) {
					// no response
					// alert("Error: " + data);
				}
			});
		}
		else {
			$('#search-typeahead2').html("");
		}
	});
	
	
	$('body').click(function () {
		$('#search-typeahead2 ul').hide();
	});
	
	$(query).click(function (e) {
		e.stopPropagation();
		$('#search-typeahead2').show();
	});
	$(query).keyup(function (e) {
			if (e.keyCode == 40) {
				if ($('.searchSelected').length == 0) {
				$('#search-typeahead2 li:eq(0)').addClass('searchSelected');
				} else {
					var sele = $('.searchSelected');
					var whichone = ($('#search-typeahead2 li').index(sele) + 1);
					$('#search-typeahead2 li:eq(' + whichone + ')').addClass('searchSelected');
					$(sele).removeClass('searchSelected');	
			}
			return false;
		} else if (e.keyCode == 38) {
				if ($('.searchSelected').length==0) {
				$('#search-typeahead2 li:last-child').addClass('searchSelected');
				} else {
					var sele = $('.searchSelected');
					var whichone = ($('#search-typeahead2 li').index(sele)-1);
					$('#search-typeahead2 li:eq(' + whichone + ')').addClass('searchSelected');
					$(sele).removeClass('searchSelected');	
			}
			return false;
		}
	});
	$(query).keydown(function (e) {
	if ((e.keyCode == 13) && ($('.searchSelected').length > 0)) {
		window.location = $('.searchSelected a').attr('href');
		return false;
	};
});
};


/**
 * LEGACY Opens a popup window with a color parameter.
 * @see windowPop
 */
function windowPopColor (pathName, colorParameter, winName, intWidth, intHeight, intScroll, intLocation, resize) {
	if (colorParameter != null) {
		colorParameter = colorParameter.replace(/,_/g, "_");
	}
	windowPop (pathName + "&" + colorParameter, winName, intWidth, intHeight, intScroll, intLocation, resize);
}
/**
 * LEGACY Opens a popup window.
 * TODO: remove!
 */
function windowPop (pathName, winName, intWidth, intHeight, intScroll, intLocation, resize) {
	if (resize == null) {
		var resizable = "yes";
	} else {
		var resizable = resize;
	}
	if (document.all) {
		var xWidth = screen.width, yHeight = screen.height;
	} else {
		if (document.layers) {
			var xWidth = window.outerWidth, yHeight = window.outerHeight;
		} else {
			var xWidth = 800, yHeight = 600;
		}
	}
	var xOffset = (xWidth - intWidth) / 2, yOffset = (yHeight - intHeight) / 2;
	var features;
	
	features = 'width=' + intWidth;
	features += ',height=' + intHeight;
	features += ',screenX=' + xOffset;
	features += ',screenY=' + yOffset;
	features += ',top=' + yOffset;
	features += ',left=' + xOffset;
	features += ',scrollbars=' + intScroll;
	features += ',toolbar=no';
	features += ',location=' + intLocation;
	features += ',resizable=' + resizable;
	//alert(features)
	
	var newWindow;
	newWindow = window.open(pathName, winName, features);
	newWindow.focus();
}

