/*
 * All java script logic for the application.
 *
 * The code relies on the jQuery JS library to
 * be also loaded.
 */

var app = (function(jQuery){
	if (!jQuery) {
		alert('jQuery is missing. This script required jQuery.');
		return null;
	}

	function appendTbWindow () {
		jQuery('#TB_window #TB_title').append('<div class="TB_topCenter"><div class="TB_topLeftCorner TB_shadow"></div><div class="TB_topRightCorner TB_shadow"></div></div>');
		jQuery('#TB_window').append('<div class="TB_rightContent">&nbsp;</div>');
		jQuery('#TB_window').append('<div id="TB_footer"><div class="TB_bottomCenter"><div class="TB_bottomLeftCorner TB_shadow">&nbsp;</div><div class="TB_bottomRightCorner TB_shadow">&nbsp;</div></div></div>');
		jQuery('.TB_rightContent').attr('style','height:' + jQuery('#TB_window #TB_ajaxContent').outerHeight() + 'px');
	}	
	
	return {
		
		URLs			: {}, // holds dw specific urls, check htmlhead.isml for some examples
		resources		: {},  // resource strings used in js
		constants		: {},
		containerId		: "content",
		ProductCache	: null,  // app.Product object ref to the current/main product
		clearDivHtml	: "<div class=\"clear\"><!-- W3C Clearing --></div>",
		currencyCodes	: {}, // holds currency code/symbol for the site

		// default dialog box settings
		dialogSettings: {
				bgiframe: true, // this is required mainly for IE6 where drop downs bleed into dialogs!!! it depends on 
				autoOpen: false,
				buttons: {},
				modal: true,
				overlay: {
		    		opacity: 0.5,
		     		background: "black"
				},
		    	height: 530,
		    	width: 800,
		    	title: '',
		    	// show: "slow", This is causing dialog to break in jquery 1.3.2 rel, show: "slide" works but not desired
		    	hide: "normal",
		    	resizable: false
		},

		// default tooltip settings
		tooltipSettings: {
				delay: 0,
				showURL: false,
				extraClass: "tooltipshadow tooltipshadow02",
				top: 15,
				left: 5
		},

		// global form validator settings
		validatorSettings: {
			errorClass : 'errorclient',
			errorElement: 'span',
			
		    onfocusout: function(element) {
				if ( !this.checkable(element) ) {
					this.element(element);
				}				
			}
		},
		
		// app initializations called from jQuery(document).ready at the end of the file
		init: function() {
			// register initializations here
			
			// quick view dialog div
			jQuery("<div/>").attr("id", "QuickViewDialog").html(" ").appendTo(document.body);

			// micicart object initialization
			this.minicart.init();
			
			// minibasket controls initialization
			this.minibasket.init();
			
			// execute unobtrusive js code
			this.execUjs();
			
			jQuery(document).ready(function() {
			    jQuery("a.smoothbox").each(function(){
			        jQuery(this).bind('click', function (event) {
			        	event.preventDefault();
			        	modalBoxCreate(this.href, null, true);
			        });
			    });
			});
		},
	
		// sub namespace app.ajax.* contains application specific ajax components
		ajax: {
			Success: "success",
			currentRequests: {}, // request cache

			// ajax request to get json response
			// @param - reqName - String - name of the request
			// @param - async - boolean - asynchronous or not
			// @param - url - String - uri for the request
			// @param - data - name/value pair data request
			// @param - callback - function - callback function to be called
			getJson: function(options) {
				var thisAjax = this;

				// do not bother if the request is already in progress
				// and let go null reqName
				if (!options.reqName || !this.currentRequests[options.reqName]) {
					this.currentRequests[options.reqName] = true;
					if(options.async == "undefined") options.async = true;
					// make the server call
					jQuery.ajax({
						contentType: "application/json; charset=utf-8",
						dataType: "json",
						url		: options.url,
						cache	: true,
						async	: options.async,
						data	: options.data,

						success: function(response, textStatus) {
							thisAjax.currentRequests[options.reqName] = false;

							if (!response.Success) {
								// handle failure
							}

							options.callback(response, textStatus);
						},

						error: function(request, textStatus, error) {
							if (textStatus === "parsererror") {								
								alert(app.resources["BAD_RESPONSE"]);
							}
							
							options.callback({Success: false, data:{}});
						}
					});
				}
			},

			// ajax request to load html response in a given container
			// @param - reqName - String - name of the request
			// @param - url - String - uri for the request
			// @param - data - name/value pair data request
			// @param - callback - function - callback function to be called
			// @param - selector - string - id of the container div/span (#mycontainer) - it must start with '#'
			load: function(options) {

				var thisAjax = this;

				// do not bother if the request is already in progress
				// and let go null reqname
				if (!options.reqName || !this.currentRequests[options.reqName]) {
					this.currentRequests[options.reqName] = true;
					// make the server call
					jQuery.ajax({
						dataType: "html",
						url		: options.url,
						cache	: true,
						data	: options.data,

						success: function(response, textStatus) {
							thisAjax.currentRequests[options.reqName] = false;
							
							if (options.selector) {
								jQuery(options.selector).html(response);
							}

							(options.callback != undefined ? options.callback(response, textStatus): null)
						},

						error: function(request, textStatus, error) {
							if (textStatus === "parsererror") {								
								alert(app.resources["BAD_RESPONSE"]);
							}

							options.callback(null, textStatus);
						}
					});
				}
			}
		},
		
		checkTbWindow : function () {
			if (jQuery('#TB_window')[0] && jQuery('#TB_title')[0] && jQuery('#TB_window #TB_ajaxContent').outerHeight() != 0) {
				appendTbWindow();
			} else {
				setTimeout('app.checkTbWindow()', 100);
			}
		},
		
		/**
		 * grab anything inside a hidden dom element and append it to its immediate previous sibling
		 * as data attribute i.e. jQuery().data("data", hiddenStr)
		 * if the hidden data specifies json in the class then this routine would attempt to 
		 * convert the hidden data into json object before adding it as data attribute.
		 * after adding the data, the hidden span/element is removed from the DOM.
		 */
		hiddenData : function() {
			jQuery.each(jQuery(".hiddenData"), function() {
				var hiddenStr = jQuery(this).html();
				
				if (hiddenStr === "") {
					return;
				}
				
				// see if its a json string
				if (jQuery(this).hasClass("json")) {
					// try to parse it as a json
					try {
						hiddenStr = window["eval"]("(" + hiddenStr + ")");
					}
					catch(e) {}				
				}
				
				jQuery(this).prev().data("data", hiddenStr);
				
				jQuery(this).remove();
			});
		},
		
		/**
		 * Unobtrusive js api calls go here.
		 */

		execUjs: function() {
			// process hidden data in the html markup and cnnvert it into data object(s)
			this.hiddenData();
		},
		
		tabs : function () {
			var head = new Object();
			var contents = new Object();
			var bindTabsClick = function () {
				jQuery.each(head.children('.tabHead'), function () {
					jQuery(this).click(function () {
						// check if there is a jScrollPane applied
						if(contents.children('div:first-child').is('.jspContainer')) {
							contents = contents.children('div:first-child').children('div:first-child');
						}
						contents.children('.visible').removeClass('visible');
						head.children('.selected').removeClass('selected');
						var clickedTab = jQuery(this);
						clickedTab.addClass('selected');
						var parentContainer = clickedTab.closest(".tabsContainer")
						var newContent = parentContainer.find("#" + this.id + "_content");
						newContent.addClass('visible');
						// scroll the jScrollPane to the top
						if(jspApi != null && jspApi != undefined) {
							jspApi.scrollToPercentY(0);
							jspApi.reinitialise();
						}
					});
				});
			};
			return {
				init : function (config) {
					head = jQuery(config.head);
					contents = jQuery(config.contents);
					bindTabsClick();
				}
			}
		},
		
		// this functionality requires special html structure.
		productSlider : function () {
			// * required : this defines the type of the slider. It can be vertical or horizontal.
			var type = new String();
			// * required : this is the moving part.
			var container = new Object();
			// * required : this indicates the number of visible elements
			var visibleElements = new Number();
			// * required : this are the buttons that moves the slider. They need to be out side of the moving part.
	 		var buttons = {
				firstSide : new Object(),
				secondSide : new Object()
			};
			// this are calculated properties
			var _step = new Number();
			var _stepsMoved = new Number(0);
			var _stepTotal = new Number();
			var _visibleDimention = new Number();
			var _elementOuterDimention = new Number();
			var _isLoop = false;
			var isProductModule = false;
		

	        var lock = false;
	
			var moveFirstSide = function () {
				
				if ( !hasLock() )
				{
					setLock();
					
					if (_stepsMoved > 0) 
					{
						_stepsMoved --;
						
						if (type == 'vertical') 
						{
							container.animate({'top': "+=" + _step}, "slow", null, releaseLock);
						} 
						else 
						{
							container.animate({'left': "+=" + _step}, "slow", null, releaseLock);
						}
					}
					else if ( _isLoop )
					{
						_stepsMoved = _stepTotal - (visibleElements * 2) - 1;
						container.css('top', -( (_stepTotal - (visibleElements*2)) * _step));
						container.animate({'top': "+=" + _step }, "slow", null, releaseLock);				
					}
					else
						releaseLock();
					
					disablebuttons();
				}
					
			}
			
			var moveSecondSide = function () 
			{
				if ( !hasLock() )
				{
					if (_elementOuterDimention > _visibleDimention )
					{
						setLock();
						
						if ( (cfg.georgeCarousel == 'true' && _stepsMoved < (_stepTotal - 1) && isProductModule == true) || (_stepsMoved < _stepTotal - visibleElements)) 
						{
							_stepsMoved ++;
						
							if (type == 'vertical')
							{
								container.animate({'top': "-=" + _step}, "slow", null, releaseLock);
							} 
							else 
							{
								container.animate({'left': "-=" + _step}, "slow", null, releaseLock);
							}
						}
						else if ( _isLoop && ( _stepsMoved == _stepTotal - visibleElements ) )
						{
							_stepsMoved = visibleElements + 1;
							container.css('top', -(visibleElements * _step));
							container.animate({'top': "-=" + _step }, "slow", null, releaseLock);				
						}
						else
							releaseLock();
						
						disablebuttons();
					}
				}
			}
			
			var setLock = function() {
				lock = true;
			}
			
			var releaseLock = function() {
				lock = false;
			}
						
			var hasLock = function() {
				return lock;
			}
			
			var bindButtons = function () {
				buttons.firstSide.click(function () {
						moveFirstSide()
						return false;
				});
				buttons.secondSide.click(function () {
						moveSecondSide()
						return false;
				});
			}
			
			var disablebuttons = function () {
				
				if ( !_isLoop )
				{
					if (_stepsMoved == 0) {
						buttons.firstSide.addClass('inactiveUp');
					} else {
						buttons.firstSide.removeClass('inactiveUp');
					}
					
					if ((cfg.georgeCarousel == 'true'  && (_stepsMoved + 1) == _stepTotal && isProductModule == true) || (_stepsMoved + visibleElements) == _stepTotal) {
						buttons.secondSide.addClass('inactiveDown');
					} else {
						buttons.secondSide.removeClass('inactiveDown');
					}
				}
			}
				
			return { 
				init : function (config) {
					type = config.type;
					buttons.firstSide = jQuery(config.btn1);
					buttons.secondSide = jQuery(config.btn2);
					container = jQuery(config.container);
					
					var elementsCount;

					
					if(config.georgeCarousel == 'true') 
					{
						if (typeof(cfg.isProductModule) == 'undefined' || cfg.isProductModule != 'false') {
							isProductModule = true;
						}
						elementsCount = jQuery(container.children(".listItem"));
						container.css('width', elementsCount.length * jQuery( elementsCount[0] ).outerWidth(true) );
					}
					
					visibleElements = config.visibleElements;
					
					if ( config.isLoop == true )
					{
				        var imagesList = jQuery("a", container);
				        var imagesNumber = imagesList.size(); 
				        
				        if ( imagesNumber >= visibleElements + 1)
				        {
				        	container.prepend( imagesList.slice(imagesNumber - visibleElements - 1 + 1).clone() );
				        	container.append( imagesList.slice(0,visibleElements).clone());
				        	
				        	_isLoop = true;
				        }
				        else
				        	bindButtons();
					}
					
					
					var parent = container.parent();
					
					if(config.georgeCarousel == 'true')
					{
						_visibleDimention = type == 'vertical' ? config.visibleElements * jQuery( elementsCount[0] ).outerHeight(true) : config.visibleElements * jQuery( elementsCount[0] ).outerWidth(true)
					}else
					{
						_visibleDimention = type == 'vertical' ? parent.outerHeight() : parent.outerWidth();
					}
					
					_elementOuterDimention = type == 'vertical' ? container.outerHeight() : container.outerWidth();
					
					if(config.georgeCarousel == 'true' && isProductModule == true)
					{
						_step = parseInt(_visibleDimention);
						_stepTotal = (Math.ceil(_elementOuterDimention / _step));
					}else
					{
						_step = parseInt(_visibleDimention / visibleElements);
						_stepTotal = parseInt(_elementOuterDimention / _step);
					}
					if ( _isLoop )
					{
						container.css('top', -( visibleElements * _step) + "px");
						_stepsMoved = visibleElements;
					}
					
					var oldStyle = parent.attr('style');
					var dimention = type == 'vertical' ? 'y' : 'x';
					parent.attr('style', 'overflow-'+dimention+': hidden; ' + oldStyle);
					
					if(parent.parent().css('visibility') == 'hidden') {
						jQuery(parent.parent()).hide();
						jQuery(parent.parent()).css('visibility', 'visible');
					}
					
					if (_stepTotal > config.visibleElements || (config.georgeCarousel == 'true' && isProductModule == true && _stepTotal > 1)) {
						disablebuttons();
						bindButtons();
						buttons.firstSide.removeClass('hidden');
						buttons.secondSide.removeClass('hidden');
					}
				}
			}
		},
		
		changeImages : function () {
			var config = new Object();
			
			var thumbnailUrl = new String ();
			
			var changeImage = function (that) {
				var img = jQuery(config.image);
				thumbnailUrl = that.attr('href');
				thumbnailUrl = thumbnailUrl.split('?')[0];
				var url = thumbnailUrl + '?hei=' + config.height + '&wid=' + config.width + '&resmode=sharp&op_usm=1.1,0.5,0,0&defaultimage=default_details_George_rd';				
				img.fadeOut('fast', function () {
					jQuery(config.image).prop('src', url);
					jQuery(config.image).fadeIn('fast');
					if (config.zoomOptions) { 
						var imgContainer = img.closest('a');
						var urlBig = thumbnailUrl + '?hei=1800&wid=1400&resmode=sharp&op_usm=1.1,0.5,0,0&defaultimage=default_details_George_rd';
						imgContainer.unbind();
						imgContainer.prop('href', urlBig);
						jQuery('.productImageLink').data('jqzoom').el.largeimageloaded = false;
						jQuery('.productImageLink').data('jqzoom').load();
						imgContainer.click(function(){ return false; });
					}
				});
			}
			
			var bindClick = function (img) {
				jQuery(img).click(function () {
					var that = jQuery(img);
					if (!that.hasClass('selected')) {
						jQuery(config.container + ' a.thumbnail').removeClass('selected');
						that.addClass('selected');
						changeImage(that);
					}
					return false;
				});
			}
		
			return {
				init : function(conf) {
					config = conf;
					
					jQuery(document).ready(function () {
						jQuery.each(jQuery(config.container + ' a.thumbnail'),function() {
							bindClick(this);
						});
					});
				}
			}
		},
		
		// renders a progress indicator on the page; this function can be used
		// to indicate an ongoing progress to the user; the optional parameter "className"
		// can be used to attach an additional CSS class to the container
		showProgress : function(className) {
			var clazz = "loading";
			if (className) clazz += " " + className;			
			return jQuery("<div class=\"" + clazz + "\"/>").append(jQuery("<img/>").attr("src", app.URLs.loadingSmallImg));
		},
		
		showProgressIcon : function(className) {
			var clazz = "loading";
			if (className) {
				clazz += " " + className;
			}
			// gettting value from input hidden, it will return null of String type if there is no image path found in content library. 
			var loadingProgressImage = jQuery("#preloaderImage").val() != "null" ? jQuery("#preloaderImage").val() : app.URLs.loadingSmallImg;
			
			var browserAvailHeight = jQuery(window).height();
			var browserAvailWidth = jQuery(window).width();
			var MB_loaderTop = Math.round( ( browserAvailHeight - 186 ) / 2 );
			var MB_loaderLeft = Math.round( ( browserAvailWidth - 148 ) / 2 );
			
			var inlineStyle = 'top:'+ MB_loaderTop + 'px; left:' + MB_loaderLeft + 'px;';
	
			return jQuery("<div style=\"" + inlineStyle + "\" class=\"" + clazz + "\"/>").append(jQuery("<img/>").attr("src", loadingProgressImage));
			
		},

		// loads a product into a given container div
		// params
		// 		containerId - id of the container div, if empty then global app.containerId is used
		//		source - source string e.g. search, cart etc.
		//		label - label for the add to cart button, default is Add to Cart
		//		url - url to get the product
		//		id - id of the product to get, is optional only used when url is empty
		getProduct: function(options) { // id, source, start
			var cId 		= options.containerId || app.containerId;
			var source 		= options.source || "";
			var a2cBtnLabel = options.label || null;

			// show small loading image
			jQuery("#"+cId).html(app.showProgress("productloader"));

			var productUrl = options.url ? options.url : app.util.appendParamToURL(app.URLs.getProductUrl, "pid", options.id);
						
			productUrl = app.util.appendParamToURL(productUrl, "source", source);

			app.ajax.load({selector: "#"+cId, url: productUrl, callback: function(responseText, textStatus){
				// update the Add to cart button label if one provided
				(a2cBtnLabel != null ? jQuery("#"+cId+" .addtocartbutton:last").html(a2cBtnLabel) : '');
			}});
		},

		// sub name space app.minicart.* provides functionality around the mini cart
		minicart: {
			addProductsUrl   : '',  // during page loading, the Demandware URL is stored here
			summaryUrl : '',
			
			overlayUrl : '',
			
			init: function()
			{
				this.addProductsUrl = jQuery("#AJAXBasketItemsURL").val();
				this.summaryUrl = jQuery("#AJAXBasketSummaryURL").val();
				this.overlayUrl = jQuery("#AddToBasketOverlayURL").val();
			},
			
			// adds a product to the mini cart
			// @params
			// selectedOptions - JS Object containing productId(s) and quantity(ies)
			add: function(selectedOptions)
			{
				// get the data of the form as serialized string
				var selectedPids = selectedOptions.selectedPids.split(",");
				var selectedQtys = selectedOptions.Quantity.split(",");
				var postdata = "";
				var separator = "&";
				
				jQuery(selectedPids).each(function(i,selectedPid){
					if(i == selectedPids.length - 1)
						separator = "";
					prefix = "quantity-";
					postdata += prefix+selectedPid+"="+selectedQtys[i]+separator;
				});
				
				// handles successful add to cart
				var handlerFunc = function(req)	{
					
					// Show Overlay. Here ?!?
					var browserAvailHeight = jQuery(window).height();
					var mbH = browserAvailHeight - 80;
					mbH = ( mbH > 645 ) ? 645 : mbH; // 645px max height for add2basket overlay 
					var mbW = 820; // 820px fixed width for add2basket overlay

					modalBoxCreate(app.minicart.overlayUrl + "&" + postdata + '&height=645&width=' + mbW + '&mbW=' + mbW + '&mbH=' + mbH);						
															
					// replace the content
					jQuery('#basketContents').html(req);
					jQuery.ajax({
						type	: "POST", 
						url		: app.minicart.summaryUrl,
						cache	: true,
						data	: postdata,
						success	: function(e) {
							jQuery('.basketSummary').html(e);			
						},
						error	: errFunc
					});
										
				}

				// handles add to cart error
				var errFunc = function(req) {
					
				}

				// add the product
				jQuery.ajax({
					type	: "POST",
					url		: app.minicart.addProductsUrl,
					cache	: true,
					data	: postdata,
					success	: handlerFunc,
					error	: errFunc
				});
				
			}
		},

		// the minibasket shown in the page header
		minibasket: {
			miniBasketDiv : null,
			miniBasketControls : null,
			basketContents : null,
			closeBtns : null,
			expandBtns : null,
			basketWrapper : null,
			timer : null,
			closeTime : 3000,
			
			init: function()
			{
				this.miniBasketDiv = jQuery('#minicart');
				this.miniBasketControls = this.miniBasketDiv.find('div.basket_showToggle a');
				this.basketContents = this.miniBasketDiv.find('.basketContent');
				this.closeBtns = this.miniBasketDiv.find('div.basket_showToggle a.closeMinicart');
				this.expandBtns = this.miniBasketDiv.find('div.basket_showToggle a.expandMinicart');
				this.basketWrapper = this.miniBasketDiv.find('.basketContentWrapper');
				this.miniBasketDiv.removeClass('mouseHover');
				
				this.insertBasketControls();
			
				/*
				var miniBasketDiv = jQuery('#minicart');
				var basketContents = miniBasketDiv.find('.basketContent');
				var miniBasketControls = miniBasketDiv.find('div.basket_showToggle a');
				var closeBtns = miniBasketDiv.find('div.basket_showToggle a.closeMinicart');
				var expandBtns = miniBasketDiv.find('div.basket_showToggle a.expandMinicart');
				
				miniBasketDiv.removeClass('mouseHover');

				closeBtns.bind('click', function() {
					if(miniBasketDiv.hasClass("open")) {
						miniBasketDiv.removeClass("open");
						basketContents.animate({"height": 0}, 400, 'linear', function() {
							miniBasketDiv.removeClass("closing");
							miniBasketDiv.addClass("closed");
						});
						miniBasketDiv.addClass("closing");
					}
				});
				
				expandBtns.bind('click', function() {
					if(miniBasketDiv.hasClass("closed")) {
						var basketContentsHeight = 0;
						basketContents.children().each(function() {
							basketContentsHeight += jQuery(this).outerHeight(true);
						});
						miniBasketDiv.removeClass("closed");
						basketContents.animate({"height": basketContentsHeight}, 400, 'linear', function() {
							miniBasketDiv.removeClass("opening");
							miniBasketDiv.addClass("open");
						});
						miniBasketDiv.addClass("opening");
					}
					return false;
				});*/
			},
			
			showComplete: function() {
				app.minibasket.miniBasketDiv.removeClass("opening");
				app.minibasket.miniBasketDiv.addClass("open");
			},
			
			closeComplete: function() {
				app.minibasket.miniBasketDiv.removeClass("closing");
				app.minibasket.miniBasketDiv.addClass("closed");
			},
			
			insertBasketControls: function() {
				app.minibasket.miniBasketDiv.removeClass('mouseHover');

				app.minibasket.closeBtns.bind('click', function(event) {
					app.minibasket.closeBasket();
					event.preventDefault();
				});
				
				app.minibasket.expandBtns.bind('click', function(event) {
					app.minibasket.showBasket();
					event.preventDefault();
				});
				
				app.minibasket.basketWrapper.bind('mouseleave', function() {
					if(app.minibasket.miniBasketDiv.hasClass("open")) {
						app.minibasket.timer = setTimeout(function() { app.minibasket.closeBasket();},app.minibasket.closeTime);
					}
					return false;
				});
				
				app.minibasket.basketWrapper.bind('mouseenter', function() {
					if(app.minibasket.timer) {
						clearTimeout(app.minibasket.timer);
					}
					return false;
				});
			},
			
			showBasket: function() {
				if(app.minibasket.miniBasketDiv.hasClass("closed")) {
					var basketContentsHeight = 0;
					app.minibasket.basketContents.children().each(function() {
						basketContentsHeight += jQuery(this).outerHeight(true);
					});
					app.minibasket.miniBasketDiv.removeClass("closed");
					app.minibasket.basketContents.animate({"height": basketContentsHeight}, 400, 'linear', this.showComplete);
					app.minibasket.miniBasketDiv.addClass("opening");
				}
			},
			
			closeBasket: function() {
				if(app.minibasket.miniBasketDiv.hasClass("open")) {
					app.minibasket.miniBasketDiv.removeClass("open");
					app.minibasket.basketContents.animate({"height": 0}, 400, 'linear', app.minibasket.closeComplete);
					app.minibasket.miniBasketDiv.addClass("closing");
				}
			},
			
			autoShowCloseBasket: function() {
				app.minibasket.showBasket();
				app.minibasket.timer = setTimeout(function() { app.minibasket.closeBasket();},app.minibasket.closeTime);
			}

		},

		// Product quick view object
		quickView: {
			// bind browser events
			// options
			// buttonSelector - css selector for the quickview button
			// imageSelector - css selector for product image
			// buttonLinkSelector - css selector for quickview button link (a tag)
			// productNameLinkSelector - css selector for product name link (a tag)
			bindEvents: function(options) {
				// hide quickview buttons
				jQuery(options.buttonSelector).hide();

				// hovering
				jQuery(options.imageSelector).hover(
					function(e) {
						jQuery(this).children(options.buttonSelector).show();
						return false;
					},
					function(e) {
						jQuery(this).children(options.buttonSelector).hide();
						return false;
					}
				);

				// click binding for quick view
				jQuery(options.buttonLinkSelector).click(function(e) {
					app.quickView.show({url: this.href, source: "quickview"});
					return false;
				});

				/*
				To make bookmarking and browser back-button work correctly the browser URL needs 
				to change. To force that change we do a full-page load (not AJAX) when going from 
				search result page to product detail page.
				The implementation supports loading the product detail content with AJAX: just 
				uncomment this code block to bind the event handler.
				
				// click binding for name link
				if(options.productNameLinkSelector) {
					jQuery(options.productNameLinkSelector).click(function(e) {
						app.getProduct({url: this.href, source: "search"});
						return false;
					});
				}
				*/
			},

			// show quick view dialog and send request to the server to get the product
			// options.source - source of the dialog i.e. search/cart
			// options.url - product url
			show: function(options) {
				app.createDialog({id: 'QuickViewDialog', options: {
			    	height: 530,
			    	width: 800,
			    	dialogClass: 'quickview',
			    	title: 'Product Quickview',
			    	resizable: false
				}});

			    jQuery('#QuickViewDialog').dialog('open');
			    app.getProduct({containerId: "QuickViewDialog", source: options.source, url: options.url, label: options.label});
			},
			// close the quick view dialog
			close: function() {
				jQuery('#QuickViewDialog').dialog('close');
			}
		},

		// helper method to create a dialog with the given options
		// options - dialog box options along with id of the container
		createDialog: function(options) {
			jQuery('#'+options.id).dialog(jQuery.extend({}, app.dialogSettings, options.options));
		},

		// shows tooltip popup
		// options
		// id - id of the container
		// options - tooltip popup options
		tooltip: function(options) {
			if (options.id.charAt(0) !== '#') {
				options.id = "#"+options.id;
			}
			jQuery(options.id).tooltip(jQuery.extend({}, app.tooltipSettings, options.options));
		},
		
		/**
		 * Unobtrusively build tooltips on the page.
		 * it looks for a tooltip class anchor which contains a div with tooltip-body class as the body container.
		 */
		tooltipDefault: function () {	 
			 jQuery(document).ready(function() {
				jQuery(".tooltip").tooltip(jQuery.extend({}, app.tooltipSettings, {	
						bodyHandler: function() {
							return jQuery(this).children(".tooltip-body").html();
						}
					}
				));
			 });			
		},

		// validation plugin intialization
		validator: function() {
			// override default required field message
			jQuery.validator.messages.required = function($1, ele, $3) {
				return "";
			};
			
			/**
			 * Add phone validation method to jQuery validation plugin.
			 * Text fields must have 'phone' css class to be validated as phone
			 * phoneUS is copied from http://docs.jquery.com/Plugins/Validation/CustomMethods/phoneUS
			 */
			jQuery.validator.addMethod("phone", function(phone_number, element) {
				// find out the country code
				var data 	= jQuery(element).data("data");
				var country = (data && data.country && data.country != "") ? data.country : "US"; // default to US phone validation
				
				// preserve this instance
				var that = this;
				
				// country specific phone validation handlers
				var phoneCA,
					phoneUS = phoneCA = function() {
						phone_number = phone_number.replace(/\s+/g, ""); 
						return that.optional(element) || phone_number.length > 9 &&
							phone_number.match(/^(1-?)?(\([2-9]\d{2}\)|[2-9]\d{2})-?[2-9]\d{2}-?\d{4}$/);
					}
				
				window["eval"]("var phoneHandler = (typeof phone" + country + " != 'undefined') ? phone"+country+": null;");
				
			    // call the country specific phone validation handler
				return (phoneHandler && typeof phoneHandler == "function" ? phoneHandler() : true);
			}, app.resources["INVALID_PHONE"]);

			/**
			 * Add positive number validation method to jQuery validation plugin.
			 * Text fields must have 'positivenumber' css class to be validated as positivenumber
			 * it validates a number and throws error if it is below 0 or if it is not a number.
			 */
			jQuery.validator.addMethod("positivenumber", function(value, element) {
				if (value == '') return true;				
				return /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/.test(value) && Number(value) >= 0;
			}, ""); // "" should be replaced with error message if needed
			
			// register form validator for form elements
			// except for those which are marked "suppress"
			jQuery.each(jQuery("form:not(.suppress)"), function() {
				jQuery(this).validate(app.validatorSettings);
			});
		},

		/**
		 * grab anything inside a hidden dom element and append it to its immediate previous sibling
		 * as data attribute i.e. jQuery().data("data", hiddenStr)
		 * if the hidden data specifies json in the class then this routine would attempt to 
		 * convert the hidden data into json object before adding it as data attribute.
		 * after adding the data, the hidden span/element is removed from the DOM.
		 */
		hiddenData : function() {
			jQuery.each(jQuery(".hiddenData"), function() {
				var hiddenStr = jQuery(this).html();
				
				if (hiddenStr === "") {
					return;
				}
				
				// see if its a json string
				if (jQuery(this).hasClass("json")) {
					// try to parse it as a json
					try {
						hiddenStr = window["eval"]("(" + hiddenStr + ")");
					}
					catch(e) {}				
				}
				
				jQuery(this).prev().data("data", hiddenStr);
				
				jQuery(this).remove();
			});
		},
		
		/**
		 * Process country drop downs and attach a change listener so that phone field 
		 * can be validated properly based on the currently selected country.
		 */
		addCountryListener: function() {
			var countryHandler = function(e) {
				var selectedCountry = this.options[this.selectedIndex].value;
				// for each field of type phone in the current form, set its country as a data attribute
				// to be used while doing phone field validatiaon see app.validator addMethod.
				jQuery(this).parents("form:first").find("input.phone").each(function() {
					var data = jQuery(this).data("data");
					var currentData = (data && typeof data == 'object') ? data : {};
					currentData.country = selectedCountry;
					jQuery(this).data("data", currentData);
				});						
			}
			jQuery("select.country").change(countryHandler).each(countryHandler);
		},
		
		/**
		 * Unobtrusive js api calls go here.
		 */
		 execUjs: function() {
			// process hidden data in the html markup and cnnvert it into data object(s)
			this.hiddenData();
			
			// initialize form validator plugin
			// this.validator();
			
			// process country form fields and attach listeners
			// this.addCountryListener();
			
			// process tooltips on the page
			// this.tooltipDefault();
		},
		
		// capture recommendation of each product when it becomes visible in the carousel
		captureCarouselRecommendations : function(c, li, index, state) {
			jQuery(li).find(".captureproductid").each(function() {
				dw.ac.capture({id:this.innerHTML, type:dw.ac.EV_PRD_RECOMMENDATION});
			});
		},

		// sub namespace app.producttile.* contains utility functions for product tiles
		producttile : {
			// initializes all product tiles contained in the current page
			initAll: function() {
				// bind quick view button toggling and click
				var quickViewOptions = {
					buttonSelector: "div.producttile div.quickviewbutton",
					imageSelector: "div.producttile div.image",
					buttonLinkSelector: "div.producttile div.quickviewbutton a"
				};
				app.quickView.bindEvents(quickViewOptions);
				
				// prepare swatch palettes and thumbnails
				jQuery("div.producttile div.swatches div.invisible").hide();
				jQuery("div.producttile div.swatches a.swatch img.hiddenthumbnail").hide();
				
				// show the palette
				jQuery("div.producttile div.swatches > a").click(function(e) {
					var cont = jQuery(this).parent().find("div.palette");
					cont.show().focus();
					return false;
				});
				
				// hide the palette
				jQuery("div.producttile div.swatches div.invisible").mouseout(function(e) {
					// fix for event bubbling (http://www.quirksmode.org/js/events_mouse.html)
					if(!e) var e = window.event;
					var tg = (window.event) ? e.srcElement : e.target;
					if(tg.nodeName != 'DIV') return;
					var reltg = (e.relatedTarget) ? e.relatedTarget : e.toElement;
					while(reltg != tg && reltg.nodeName != 'BODY')
						reltg = reltg.parentNode
					if (reltg == tg) return;
					
					// mouseout took place when mouse actually left layer
					// handle event now
					jQuery(this).hide();
					return false;
				});
				
				// thumb nail toggling
				jQuery("div.producttile div.swatches div.palette a.swatch").bind("mouseover mouseout", function(e) {
					var swatch = jQuery(this);
					app.producttile.toggleVariationThumbnail(swatch);
				});
				
				// color swatch selection
				jQuery("div.producttile div.swatches div.palette a.swatch").click(function(e) {
					var swatch = jQuery(this);
					app.producttile.selectVariation(swatch);
					// omit following the swatch link
					return false;
				});
			},

			// selects a certain variation in a product tile, replaces the current image with
			// the correct variation image, changes the link to the detail
			// page and the quick view
			selectVariation : function(swatch) {
				// get the new and the original image
				var currentImg = jQuery(swatch.parents()[3]).find(".productimage img");
				var newImg = swatch.children("img.hiddenthumbnail");
				if(!currentImg || !newImg) return;
				
				// get the anchors
				var nameAnchor = swatch.parents(".producttile").find(".name a");
				var quickViewAnchor = swatch.parents(".producttile").find(".quickviewbutton a");
				var imageAnchor = swatch.parents(".producttile").find(".productimage a");
				
				// change the link url to the detail page and quick view
				var newUrl = swatch.attr("href");
				nameAnchor.attr("href", newUrl);
				quickViewAnchor.attr("href", newUrl);
				imageAnchor.attr("href", newUrl);
				
				// remove all current markers
				jQuery(swatch.parents()[0]).find("a.swatch").removeClass("selected");
				
				// mark swatch as selected
				swatch.addClass("selected");
				// we just remove the markers at the images; the actual elements
				// are correct, since they were already swapped by mouse over
				currentImg.removeClass("temp original");
				newImg.removeClass("temp original");
			},

			// shows the thumb nail of a product; this function is used to
			// temporally display a new image and restore the original one
			toggleVariationThumbnail : function(swatch) {
				// get the new and the original image
				var currentImg = jQuery(swatch.parents()[3]).find(".productimage img");
				var newImg = swatch.children("img.hiddenthumbnail");
				if(!newImg || !currentImg) return;
				
				var selectedSwatch = jQuery(swatch.parents()[0]).find("a.selected");
				var selectedImg = selectedSwatch.children("img.hiddenthumbnail");

				// we do nothing in case the swatch is already selected
				if(swatch.hasClass("selected")) return;
				
				if(currentImg.hasClass("temp")) {
					// current image is just a temp image, restore original
					var newCopy = selectedImg.clone().show().removeClass("original hiddenthumbnail");
					currentImg.replaceWith(newCopy[0]);
				} else {
					// we create a copy of the swatch image, replace
					// the current and mark it with classes
					var newCopy = newImg.clone().show().addClass("temp").removeClass("hiddenthumbnail");
					currentImg.replaceWith(newCopy[0]);
				}
			}
		},

		// sub namespace app.util.* contains utility functions
		util : {
			// disables browser auto completion for the given element
			disableAutoComplete : function(elemId) {
				jQuery("#"+elemId).attr("autocomplete", "off");
			},

			// trims a prefix from a given string, this can be used to trim
			// a certain prefix from DOM element IDs for further processing on the ID
			trimPrefix : function(str, prefix) {
				return str.substring(prefix.length);
			},

			// appends the parameter with the given name and
			// value to the given url and returns the changed url
			appendParamToURL : function(url, name, value) {
				var c = "?";
				if(url.indexOf(c) != -1) {
					c = "&";
				}
				return url + c + name + "=" + encodeURIComponent(value);
			},

			// dynamically loads a CSS file
			loadCSSFile : function(url) {
				var elem = document.createElement("link");
				elem.setAttribute("rel", "stylesheet");
				elem.setAttribute("type", "text/css");
				elem.setAttribute("href", url);

				if(typeof elem != "undefined") {
					document.getElementsByTagName("head")[0].appendChild(elem);
					app.util.loadedCSSFiles.push(url);
				}
			},

			// array to keep track of the dynamically loaded CSS files
			loadedCSSFiles : [],

			// removes all dynamically loaded CSS files
			clearDynamicCSS : function() {
				for(var i=0; i<app.util.loadedCSSFiles.length; i++) {
					app.util.unloadCSSFile(app.util.loadedCSSFiles[i]);
				}
			},

			// dynamically unloads a CSS file
			unloadCSSFile : function(url) {
				var candidates = document.getElementsByTagName("link");
				for(var i=candidates.length; i>=0; i--) {
					if(candidates[i] && candidates[i].getAttribute("href") != null && candidates[i].getAttribute("href").indexOf(url) != -1) {
						candidates[i].parentNode.removeChild(candidates[i]);
					}
				}
			},

			// checks if cookies are enabled
			cookiesEnabled : function() {
				// first we'll split this cookie up into name/value pairs
				// note: document.cookie only returns name=value, not the other components
				var all_cookies = document.cookie.split( ';' );
				var temp_cookie = '';
				var cookie_name = '';
				var cookie_value = '';
				var cookie_found = false; // set boolean t/f default f

				for ( i = 0; i < all_cookies.length; i++ )
				{
					// now we'll split apart each name=value pair
					temp_cookie = all_cookies[i].split( '=' );

					// and trim left/right whitespace while we're at it
					cookie_name = temp_cookie[0].replace(/^\s+|\s+$/g, '');

					// if the extracted name matches the session cookie name 
					if ( cookie_name == 'sid' )
					{
						// we need to handle case where cookie has no value but exists (no = sign, that is):
						if ( temp_cookie.length > 1 )
						{
							cookie_value = unescape( temp_cookie[1].replace(/^\s+|\s+$/g, '') );
						}

						if (cookie_value.length > 0)
						{
							cookie_found = true;
							break;
						}
					}
					temp_cookie = null;
					cookie_name = '';
				}
				return cookie_found;
			},
			
			/**
			 * IE 6 multiple button submit issue work around.
			 * when a form has multiple buttons of submit type, then IE 6 submits all of them
			 * whenever form is submitted. e.g. Remove on cart page would remove the wrong item
			 * (always first item in the cart) because IE 6 submits all form data which includes all 
			 * remove links!!!
			 * the workaorund is to disable all buttons except the one which is being clicked.
			 * it should only be called for IE 6 (check out htmlhead.isml for usage)
			 */
			ie6ButtonFix: function() {
				jQuery('button').click(function(){
		        	// disable all buttons
		        	jQuery(this.form).find('button').attr("disabled", true);
		        	// enable the one being clicked
		            jQuery(this).attr("disabled", false);
			    });
			}
		},
		
		// sub namespace app.dialog.* provides convenient functions to handle dialogs
		// note, that this code relies on single dialog modals (multi dialog, e.g. modal in modal is not supported)
		dialog : {
			// opens a dialog using the given url
			open : function(url, title) {
				// create the dialog container if not present already
				if(jQuery("#dialogcontainer").length == 0) {
					jQuery(document.body).append("<div id=\"dialogcontainer\"></div>");
				}

				// set a default title
				title = title || "Dialog";

				// finally load the dialog, set the dialog title
				app.ajax.load({
					selector: "#dialogcontainer",
					url: url,
					callback: function() {
						app.dialog.checkOpen();
						app.dialog.setTitle(title);
					}
				});
			},

			// initializes the dialog with common dialog actions, like closing upon canceling
			// use this function in the dialog rendering template to re-bind common actions
			// upon dialog reload
			init : function() {
				jQuery(document).ready(function() {
					// binds the action to all buttons defining an action through the "name" attribute
					jQuery("#dialogcontainer button").each(function() {
						jQuery(this).click(function() {
							var action = jQuery(this).attr("name");
							if(action) {
								app.dialog.submit(action);
							}
							return false;
						});
					});

					// cancel button binding
					jQuery("#dialogCancelBtn").click(function() {
						app.dialog.close();
						return false;
					});
				});
			},

			// sets the title of the dialog
			setTitle : function(title) {
				jQuery("#dialogcontainer").dialog("option", "title", title);
			},

			// checks, if the dialog is in the state "open" and sets the state if not presently set
			// this function is implicitly called by app.dialog.open(url, title) in order to initialize
			// the dialog properly; use this function to recover the "open" state of a dialog
			checkOpen : function() {
				if(!jQuery("#dialogcontainer").dialog("isOpen"))
				{
					jQuery("#dialogcontainer").dialog({
						bgiframe: true,
						autoOpen: false,
						modal: true,
						overlay: {
				    		opacity: 0.5,
				     		background: "black"
						},
				    	height: 425,
				    	width: 460,
				    	resizable: false
					});
					jQuery("#dialogcontainer").dialog("open");
				}
			},

			// closes the dialog and triggers the "close" event for the dialog
			close : function() {
				jQuery("#dialogcontainer").dialog("close");
				jQuery(document.body).trigger("dialogClosed");
			},

			// attaches the given callback function upon dialog "close" event
			onClose : function(callback) {
				if(callback != undefined) {
					jQuery(document.body).bind("dialogClosed", callback);
				}
			},

			// triggers the "apply" event for the dialog
			triggerApply : function() {
				jQuery(document.body).trigger("dialogApplied");
			},

			// attaches the given callback function upon dialog "apply" event
			onApply : function(callback) {
				if(callback != undefined) {
					jQuery(document.body).bind("dialogApplied", callback);
				}
			},

			// triggers the "delete" event for the dialog
			triggerDelete : function() {
				jQuery(document.body).trigger("dialogDeleted");
			},

			// attaches the given callback function upon dialog "delete" event
			onDelete : function(callback) {
				if(callback != undefined) {
					jQuery(document.body).bind("dialogDeleted", callback);
				}
			},

			// submits the dialog form with the given action
			submit : function(action) {
				// set the action
				jQuery("#dialogcontainer form").append("<input name=\"" + action + "\" type=\"hidden\" />");

				// serialize the form and get the post url
				var post = jQuery("#dialogcontainer form").serialize();
				var url = jQuery("#dialogcontainer form").attr("action");

				// post the data and replace current content with response content
		  		jQuery.ajax({
				   type: "POST",
				   url: url,
				   data: post,
				   dataType: "html",
				   success: function(data){
		  				jQuery("#dialogcontainer").empty().html(data);
				   },
				   failure: function(data) {
					   alert(app.resources["SERVER_ERROR"]);
				   }
				});
			}
		},
		getUrlVars : function (url) {
		    var hash;
		    var hashes = url.slice(url.indexOf('?') + 1).split('&');
		    var json = '({';
		    for(var i = 0; i < hashes.length; i++) {
		        hash = hashes[i].split('=');
		        json += '' + hash[0] + ': "' + hash[1] + '"' + (i != hashes.length-1 ? ',' : ',length:' + hashes.length +'})');
		    }
		    return eval(json);
		}
	}
})(jQuery);

//application initialization on dom ready

jQuery(document).ready(function(){
	app.init();
});
