(function(app){
	if (app) {
		// add Product object to dw namespace
		app.Product = function(response) {
			// product private data
			//PJN for zoom
			var extralargeURL = "";
			var largeURL = "";			
			
			var model 			= response.data;

			var mainAttrGroup	= null;

			var myContainerId	= "";

			var isLoadingVar	= false;

			var loadVariants	= function(thisProduct) {
				isLoadingVar = true;
				// build the url and load variants data
				var url = app.util.appendParamToURL(app.util.appendParamToURL(app.URLs.getVariants, "pid", thisProduct.pid), "format", "json")
				var result = app.ajax.getJson({
					url: url,
					data: {},
					callback: function(data){

						if (!data.Success) {
							return;
						}
						model.variations.variants = data.variations.variants;
						isLoadingVar = false; // we have loaded the variants
						jQuery(thisProduct).trigger("VariationsLoaded", ["loadVariants"]);
					}
				});
			}

			var loadRecommendations = function(containerId) {
				if (jQuery(containerId+" #pdpCarouselDiv ul li").length > 0) {
					jQuery(containerId+" #pdpCarouselDiv ul").jcarousel({scroll: 1});
					// create tooltips event handler
					app.tooltip({id: containerId+" #pdpCarouselDiv ul li", options: {
							bodyHandler: function() {
								return jQuery(this).children(".pdpTooltip").html();
							}
					}});
				}
			}

			var getOptionsDiv	= function(thisProduct) {

				if (model.isOption && !model.master) {

					var pdpOpt = jQuery(thisProduct.containerId+" .product_options:last select");

					pdpOpt.change(function(e){
						var vals = this.options[this.selectedIndex].value.split("%?%"); // 0 = value, 1 = price
						e.data = {id: this.id, val: vals[0], price: vals[1]};
						thisProduct.optionSelected(e);
					});
					
					// let us get the currently selected value and intilize the ui
					pdpOpt.each(function(i) {
						var vals = this.options[this.selectedIndex].value.split("%?%"); // 0 = value, 1 = price					
						thisProduct.optionSelected({data : {id: this.id, val: vals[0], price: vals[1]}});
					});
				}
			}

			var disableAddToCart = function(thisProduct) {
				//jQuery(thisProduct.containerId+" div.add-to-cart-container .addToCartBtn").attr("disabled", "disabled");				
				jQuery(thisProduct.containerId+" div.add-to-cart-container").addClass("disabled");
			};
			
			var enableAddToCart = function(thisProduct) {
				jQuery(thisProduct.containerId+" div.add-to-cart-container .addToCartBtn").attr("disabled", null);
				jQuery(thisProduct.containerId+" div.add-to-cart-container").removeClass("disabled");
			};
			
			
			var enableAddToWishlist = function(thisProduct) {
				var wishlisturl = jQuery(thisProduct.containerId+" div.add-to-cart .add-to-wishlist").attr("wurl");
				jQuery(thisProduct.containerId+" div.add-to-cart .add-to-wishlist").attr("href", wishlisturl+ "?pid=" +thisProduct.selectedVar.id);
				jQuery(thisProduct.containerId+" div.add-to-cart .add-to-wishlist").removeAttr("onclick").removeAttr("disabled").removeClass("disabled");
			};
			
			var disableAddToWishlist = function(thisProduct) {
				jQuery(thisProduct.containerId+" div.add-to-cart .add-to-wishlist").attr("onclick", "return false;").attr("disabled","disabled").addClass("disabled");				
				jQuery(thisProduct.containerId+" div.add-to-cart .add-to-wishlist").removeAttr("href");
			};

			// We don't use this
			var getAddToCartBtn = function(thisProduct) {
				var addToCartBtn = jQuery(thisProduct.containerId+" .addtocartBtn:last").click(function(e) {
					if(jQuery(this).parents(".add-to-cart-container:first").hasClass("disabled")) return false;
					
					if (model.master || model.variant) {
						if (thisProduct.selectedVar == null) {
							return false;
						}
						thisProduct.selectedOptions.pid = thisProduct.selectedVar.id;
						thisProduct.selectedOptions.masterPid = thisProduct.pid;
					}
					else {
						// check if we are adding a bundle/productset to the cart
						if (model.bundle || model.productSet) {							
							var subProducts = thisProduct.subProducts;
							var comma 		= ",";
							var tempQty 	= "";
							var subproduct 	= null;
							
							thisProduct.selectedOptions.childPids = "";
														
							if (model.productSet) {
								thisProduct.selectedOptions.Quantity = "";
							}
							
							for (var i = 0; i < subProducts.length; i++) {
								subproduct = subProducts[i];
								
								if (i == subProducts.length - 1) {
									comma = ""; // at the end of the list
								}
								
								// see if any of the sub products are variations, if so then get the selected variation id
								// from selectedVar property
								if (subproduct.variant || subproduct.master) {									
									thisProduct.selectedOptions.childPids += subproduct.selectedVar.id+comma;
								}
								else {
									thisProduct.selectedOptions.childPids += subproduct.selectedOptions.pid+comma;
								}
								
								var tempPid = subproduct.selectedOptions.pid;
								subproduct.selectedOptions.pid = null;
								// merge selected options of sub product with the main product
								thisProduct.selectedOptions = jQuery.extend({}, thisProduct.selectedOptions, subproduct.selectedOptions);
								subproduct.selectedOptions.pid = tempPid;
								
								// if it is a product set then sub products can have their separate qty
								if (model.productSet) {
									tempQty += subproduct.selectedOptions.Quantity+comma;
								}
							}
						}
						
						if (model.productSet) {
							thisProduct.selectedOptions.Quantity = tempQty;
						}
						
						thisProduct.selectedOptions.pid = thisProduct.pid;
					}

					if (model.bundle) {
						thisProduct.selectedOptions.Quantity = 1; // hard coded qty=1 when we the product is a bundle
					}
					else if (!model.productSet){
						thisProduct.selectedOptions.Quantity = jQuery(thisProduct.containerId+" .quantityinput:last").val();
					}

					if (model.productSet || thisProduct.selectedOptions.Quantity > 0) {
						jQuery(this).prev('img.busy').removeClass('noDisplay');
						disableAddToCart(thisProduct);
						var callback = function(){enableAddToCart(thisProduct)};
						if (model.source == 'cart') {
							thisProduct.selectedOptions.pliId = model.ID; // used in case of cart edit to replace existing line item
							callback = app.refreshCart;
						}
						
						var event = jQuery.Event("AddToCart");
						event.selectedOptions = thisProduct.selectedOptions;						
						
						// close the quick view when user clicks A2C.
						app.quickView.close();
												
						(jQuery.event.global["AddToCart"] == undefined || jQuery.event.global["AddToCart"] == null) ? app.minicart.add( "", thisProduct.selectedOptions, callback ) : jQuery(document).trigger(event);
					}
					return false;
				} );

				return addToCartBtn;
			}

			var getQtyBox = function(thisProduct) {				
				
				jQuery(thisProduct.containerId+" .quantityinput:last").keyup(function(e){
					var val = null;
					try {
						val = parseInt(jQuery(thisProduct.containerId+" .quantityinput:last").val());
					} catch(e){val = null};

					if (val != null) {
						thisProduct.selectedOptions.Quantity = val;
						
						setAvailabilityMsg(createAvMessage(thisProduct, val));
						
						jQuery(thisProduct).trigger("AddtoCartEnabled");
					}
				});
				if( thisProduct.variant ) {
					thisProduct.selectedOptions.Quantity = 1;
				} else {
					thisProduct.selectedOptions.Quantity = jQuery(thisProduct.containerId+" .quantityinput:last").val();
				}
				setAvailabilityMsg(createAvMessage(thisProduct, thisProduct.selectedOptions.Quantity));
			}

			var getTabs 		= function(containerId) {

				var tabsDiv = jQuery(containerId+" #pdpTabsDiv");
				tabsDiv.tabs();

				// tab print handler
				jQuery("a.printpage").click(function() {
					window.print();
					return false;
				});
			}

			var getMiscLinks 	= function(thisProduct) {
			
				if ((model.master || model.variant) && thisProduct.selectedVar == null) {
					// disable wishlist/gift registry links for master products
					jQuery(thisProduct.containerId+" .addtowishlist, "+thisProduct.containerId+" .addtoregistry").addClass("unselectable");
				}
				
				jQuery(thisProduct).bind("AddtoCartEnabled", {}, function(e, source){
					// enable wishlist/gift registry links for variant products
					jQuery(thisProduct.containerId+" .addtowishlist, "+thisProduct.containerId+" .addtoregistry").removeClass("unselectable");
				});
				
				// Add to wishlist, Add to gift registry click handler
				jQuery(thisProduct.containerId+" .addtowishlist a, "+thisProduct.containerId+" .addtoregistry a").click(function(e) {
					// append the currect selectied options to the url
					
					// create a local copy of the selected options
					var selectedOptions = jQuery.extend({}, {}, thisProduct.selectedOptions);
					
					if (model.master || model.variant) {
						if (thisProduct.selectedVar != null) {
							selectedOptions.pid = thisProduct.selectedVar.id;
						}
						else {
							return false; // do not allow master product to be added to gift registry/wishlist
						}
					}
					else {
						selectedOptions.pid = thisProduct.pid;
					}
					
					var tempUrl = this.href;
					
					if (!(tempUrl.indexOf("?") > 0)) {
						tempUrl = tempUrl + "?";
					}
					// serialize the name/value into url query string and append it to the url, make request
					window.location = tempUrl + "&" + jQuery.param(selectedOptions);
					return false;
				} );			
				
				jQuery(thisProduct.containerId+" #pdpSendToAFriend").click(function(e) {
					app.dialog.open(app.URLs.sendToFriend, app.resources.SEND_TO_FRIEND);
					return false;
				} );
			}
			
			var getRatingSection = function(containerId) {

				jQuery(containerId+" #pdpReadReview").click(function(e) {
					jQuery(containerId+" #pdpTabsDiv").tabs("select", "pdpReviewsTab");
				} );

				jQuery(containerId+" #pdpWriteReview").click(function(e) {
				} );
			}

			// based on availability status, creates a message
			// param val - the stock value to compare i.e. qty entered by user
			var createAvMessage = function(thisProduct, val) {
					
				var avStatus 	= thisProduct.getAvStatus(); // availability status
				var avMessage 	= app.resources[avStatus];
				var ats 		= thisProduct.getATS(); // get available to sell qty
				
				if (avStatus === app.constants.AVAIL_STATUS_BACKORDER) {						
					avMessage = avMessage + " <span class=\"detail\">" + jQuery.format(app.resources["IN_STOCK_DATE"], (new Date(thisProduct.getInStockDate())).toDateString() ) + "</span>";
				}
				else if (val > ats && avStatus !== app.constants.AVAIL_STATUS_NOT_AVAILABLE) {
					// display quantity left message
					avMessage = jQuery.format(app.resources["QTY_"+avStatus], ats);

					//avMessage += ", " + jQuery.format(app.resources["REMAIN_"+avStatus], val - model.ATS);						
				}
				
				return avMessage;
			}

			var setAvailabilityMsg = function(msg, willFade, colors) {
				if(msg != null & msg != "") {
				while (msg.lastIndexOf(' ')==msg.length-1) msg=msg.substring(0,msg.length-1);
				while (msg.indexOf('  ')>-1) msg=msg.replace('  ',' ');
				msg = msg.split(' ');
				msg[msg.length-2] = msg[msg.length-2]+"\xA0"+msg.pop();
				msg=msg.join(' ');
				}
				var that = jQuery(myContainerId+' .availability:last .value');
				var alertColors = {font:{low:'#333',high:'#ff0000'},back:{low:'#fff',high:'#fff'}};
				
				if(colors && colors!=null) {
					if(colors.font && colors.font.indexOf('#')==0 && (colors.font.length==7||colors.font.length==4))
						alertColors.font.high = colors.font;
					if(colors.back && colors.back.indexOf('#')==0 && (colors.back.length==7||colors.back.length==4))
						alertColors.back.high = colors.back;
				}
				
				jQuery(that).html(msg);
				jQuery(that).stop().css({color:alertColors.font.low,backgroundColor:alertColors.back.low});
				if(willFade)
				jQuery(that)
					.animate({color:alertColors.font.high,backgroundColor:alertColors.back.high},500,function(){jQuery(this)
					.animate({delay:"1"},1000, function(){jQuery(this)
					.animate({color:alertColors.font.low,backgroundColor:alertColors.back.low},1000);});});
			}

			var computePrice = function(thisProduct) {

				var price = thisProduct.selectedVar != null ? thisProduct.selectedVar.pricing.sale : model.pricing.sale;
				// calculate price based on the selected options prices
				jQuery.each(thisProduct.selectedPrice, function(){
					price = (new Number(price) + new Number(this)).toFixed(2);
				});

				return price;
			}

			// bind click handlers for prev/next buttons on pdp from search
			var getNavLinks = function() {
				// bind events
				jQuery(".productnavigation a").click(function(e) {
					app.getProduct({url: this.href, source: "search"});
					return false;
				});
			}
			
			// size chart link click binding
			var getSizeChart = function() {
				jQuery(".sizeChartLink").click(function(e){
					if (jQuery("#sizeChartDialog").length == 0) {
						jQuery("<div/>").attr("id", "sizeChartDialog").appendTo(document.body);
					}
					
					app.createDialog({id: 'sizeChartDialog', options: {
				    	height: 530,
				    	width: 800,
				    	title: 'Size Chart'
					}});
					
					jQuery('#sizeChartDialog').dialog('open');
					
					jQuery("#sizeChartDialog").load(this.href);
					
					return false;
				});
			}
			
			// Product instance
			return {
				pid					: model.ID,
				variant				: model.variant,
				master				: model.master,
				bundled				: model.bundled,
				selectedVarAttribs	: {},
				varAttributes		: {},
				selectedVar			: null,
				selectedOptions		: {}, // holds currently selected options object {optionName, selected val}
				selectedPrice		: {}, // holds price for selected options
				containerId			: null, // holds html container id of this product
				subProducts			: [], // array to keep sub products links 

				showSelectedVarAttrVal: function(varId, val) {
					jQuery(this.containerId+" .club-head div span[id='pdp"+varId+"selected']").html(val);
					this.showAllSelectedVarAttrs();
				},
				
				
				showAllSelectedVarAttrs: function() {
					var selectedstr = "";
					$.each(this.selectedVarAttribs, function(key, val) {						
						if (val != undefined) {
							selectedstr = selectedstr + " " + val;
						}
					});
					jQuery(this.containerId+" .club-head div span[id='foot']").html(selectedstr);
				},	
				
				readReviews: function() {
					jQuery(this.containerId+" #pdpTabsDiv").tabs("select", "pdpReviewsTab");
				},
				// shows product images and thumbnails
				// @param selectedVal - currently selected variation attr val
				// @param vals - total available variation attr values
				showImages: function(selectedVal, vals)  {
					var that = this;
					vals = vals || {};
					
					// show swatch related images for the current variation value					
					jQuery.each(vals, function(){
						
						var thisVal = this;
					
						if (this.val === selectedVal && this.images && !(this.val == "" || this.val == null)) {
							if (this.images.thumbnail.length > 0) {
								extralargeURL = thisVal.images.extralarge[0]; //PJN we don't make sure its the same product/variant.  Can we?
								largeURL = thisVal.images.large[0];								
								if(jQuery(that.containerId+" .feature-image .primary").attr("src") != thisVal.images.large[0])
									jQuery(that.containerId+" .feature-image .primary").attr("src", thisVal.images.large[0]).attr("orgsrc", thisVal.images.large[0]);
									
								
										
							}
													
							//make sure to show number of images based on the smallest of large or small as these have to have 1-1 correspondence.
							var noOfImages = this.images.large.length >= this.images.thumbnail.length ? this.images.thumbnail.length : this.images.large.length;
							var imgCounter = 0;
							jQuery.each(this.images.thumbnail, function(){
								imgCounter++;
								var imageInd = imgCounter;
								if (imgCounter > noOfImages - 1) {
									return;
								}
								if(jQuery('#Zoomer').length > 0)
								{
									jQuery(jQuery(that.containerId+" .thumb_view li")[imageInd]).mouseenter(function(e){
										jQuery(this).addClass('hover');
									}).mouseleave(function(){
										jQuery(this).removeClass('hover');
									});
									jQuery(jQuery(that.containerId+" .thumb_view li")[imageInd]).click(function(){
										
										
										if(jQuery(this).find(".MagicThumb-swap").length > 0)//does this thumb have a extralarge image avaliable?
										{
											//if so enable
											jQuery("#Zoomer").attr("rel", (jQuery("#Zoomer").attr("rel").replace(/ disable-zoom:true; disable-expand:true;/g,"")) );
										}
										else
										{									
											
											//if not disable
											if(jQuery("#Zoomer").attr("rel").indexOf(" disable-zoom:true; disable-expand:true;") == -1)
											jQuery("#Zoomer").attr("rel", (jQuery("#Zoomer").attr("rel") + " disable-zoom:true; disable-expand:true;") );
											
											
										}
										
											jQuery("#Zoomer").attr("href", thisVal.images.extralarge[0]);
											
											jQuery("#Zoomer img").attr("src", thisVal.images.large[0]).one("load",function(){
											
											//MagicZoomPlus.refresh();
										});
									});
								}
								else
								{
										jQuery(jQuery(that.containerId+" .thumb_view li")[imageInd]).mouseenter(function(e){
											jQuery(this).addClass('hover');
										}).mouseleave(function(){
											jQuery(this).removeClass('hover');
										}).click(function(){
											jQuery(that.containerId+" .feature-image img").attr("src", thisVal.images.large[imageInd]);
										});
								}	
							});
						}
					});
				},

				/**
				* Event handler when a variation attr is selected.
				*/
				varAttrSelected: function(e) {
					// update the selected value node
					this.showSelectedVarAttrVal(e.data.id, e.data.val);
					
					this.selectedVarAttribs[e.data.id] = e.data.val;				
					
					// if this is a deselection and user landed on a variant page then reset its variant flag 
					// as now user has deselected an attribute thus making it essentially a master product view
					if (e.data.val == null) { 
						this.variant = false;
					}

					// store this ref
					var that = this;

					// trigger update event which will update every other variation attribute i.e. enable/disable etc.

					// first reset the contents of each attribute display
					// when we have got the varriations data
					if (!isLoadingVar) {
						// find variants which match the current selection
						var selectedVarAttrVariants = e.data.val != null ? this.findVariations({id: e.data.id, val: e.data.val}): null;
						var selectedVarAttrs = jQuery.extend({}, {}, this.selectedVarAttribs);
						var validVariants = null;
						var unselectedVarAttrs = new Array();
						
						// for each selected variation attribute find valid variants
						for (var selectedVar in selectedVarAttrs) {
							if (selectedVarAttrs[selectedVar]) {
								validVariants = this.findVariations({id: selectedVar, val: selectedVarAttrs[selectedVar]}, validVariants);
							}
							else {
								unselectedVarAttrs.push(selectedVar);
							}
						}

						jQuery.each(model.variations.attributes, function () {
							if (this.id != e.data.id && selectedVarAttrs[this.id] == null) {
								that.varAttrDisplayHandler(this.id, that.filteredVariants());								
							}
							else if (e.data.val == null && selectedVarAttrs[this.id] == null) {
								that.varAttrDisplayHandler(this.id, validVariants);
							}
							else if (this.id != e.data.id && selectedVarAttrs[this.id] != null) {
								that.varAttrDisplayHandler(this.id, that.filteredVariants(this.id));
							}
							else {
								if(e.data.id == "a1")
								{
									//clear variables to be safe
									extralargeURL = "";
									largeURL = "";
								}
								// show swatch related images for the current value
								// Also returns extralaregURL and largeURL to see if zoom has new image or should be disabled below.
								extralargeURL = "";
								largeURL = "";
								that.showImages(e.data.val, this.vals);
								if(e.data.id == "a1")
								{
									if(jQuery('#Zoomer').length != 0)
									{ 
										if(extralargeURL != "" || extralargeURL != null)
										{
												if(jQuery("#Zoomer").attr("href") != extralargeURL)
												{
													jQuery("#Zoomer").attr("href", extralargeURL);
													MagicZoomPlus.refresh();
												}
											
											if(jQuery("#Zoomer").attr("rel").indexOf(" disable-zoom:true; disable-expand:true;") != -1)
											{
												jQuery("#Zoomer").attr("rel", (jQuery("#Zoomer").attr("rel").replace(" disable-zoom:true; disable-expand:true;","")) );
												
											}	
											
										}
										else
										{
											if(jQuery("#Zoomer").attr("rel").indexOf(" disable-zoom:true; disable-expand:true;") == -1)
											{
												jQuery("#Zoomer").attr("rel", (jQuery("#Zoomer").attr("rel") + " disable-zoom:true; disable-expand:true;") );
												MagicZoomPlus.refresh();
											}
										}
										
										
										
								
									
									
									}
								}
							}
						});

						// find a matching variation and update the screen
						this.selectedVar = this.findVariation(this.selectedVarAttribs);						
					}

					// lets fire refresh view event to enable/disable variations attrs
					jQuery(this).trigger("VariationsLoaded");
				},

				/**
				* go thru all variations attr and disable which are not available
				*/
				resetVariations: function() {
					if (isLoadingVar) {
						return ; // we don't have the complete data yet
					}
					var that = this;

					jQuery.each(model.variations.attributes, function () {
						jQuery.each(jQuery(that.containerId+" #pdp"+this.id+"var li a"), function(){
							var dataa = jQuery(this).data("data");
							// find A variation with this val
							var li = null;
							if( dataa ){ 
								var val = $(this).attr('title');
							
								if (that.isVariation({id:dataa.id, val:val})) {
									// found at least 1 so keep it enabled
									li = jQuery(this.parentNode);
									that.enableControl(li);//.removeClass("unselectable disabled");
								}	else {
									li = jQuery(this.parentNode);
									that.disableControl(li);//.addClass("unselectable disabled");
									li.removeClass("selected");
								}
							}
						});
					});
				},
				/**
				* given a variation attribute id and valid variants, it would adjust the ui i.e. enable/disable 
				* appropriate attribute values.
				* 
				* @param attrId - String, id of the variation attribute
				* @param validVariants - Array of json objects of valid variants for the given attribute id
				* */
				varAttrDisplayHandler: function (attrId, validVariants) {
					var that = this; // preserve this instance
					// loop thru all non-dropdown ui elements i.e. swatches e.g. color, width, length etc.
					jQuery("#pdpVarAttrDiv .swatches").each(function(){
						var swatchId = jQuery(this).data("data");  // data is id set via app.hiddenData api
						if (swatchId === attrId) {
							
							jQuery(this).find("a").each(function(){
							
								var parentLi= jQuery(this).parent();
								var thisValue = jQuery("span.detail",this).text();
								if( thisValue=="" ) thisValue=this.title;
								
								// find A variation with this val
								// instead of just checking against one value, check against 
								// all selected values except peers								
								var filteredVariants = that.findVariations({ id: attrId, val: thisValue }, validVariants);
								if (filteredVariants.length > 0) {
									// found at least 1 so keep it enabled
									that.enableControl(parentLi);//.removeClass("unselectable disabled");
								}
								else {
									// no variant found with this value combination so disable it
									that.disableControl(parentLi);//.addClass("unselectable disabled");
									
									if (parentLi.hasClass("selected")) {
										// remove the currently selected value if the value is not selectable
										that.showSelectedVarAttrVal(attrId, "");
										that.selectedVarAttribs[attrId] = null;
									}
									// remove current selection
									parentLi.removeClass("selected");
								}
							});
						}
					});
					
					// loop thru all the non-swatches(drop down) attributes
					jQuery("#pdpVarAttrDiv .variantdropdown").each(function(){
						var vaId = jQuery(this).data("data").id;  // data is id set via app.hiddenData api
						if (vaId === attrId) {
							var len = this.options.length;
							
							jQuery.each(this.options, function(){
								
								if (len > 1 && this.index == 0) {
									return; // very first option when the length is greater than 1 is 'Select ...' message so skip it
								}
								
								// find A variation with this val
								var filteredVariants = that.findVariations({id:attrId, val:this.value}, validVariants);
								
								if (filteredVariants.length > 0) {
									// found at least 1 so keep it enabled
									this.disabled = false;
								}
								else {
									// no variant found with this value combination so disable it
									this.disabled = true;
									
									if (this.selected) {
										// remove the currently selected value if the value is not selectable
										that.showSelectedVarAttrVal(attrId, "");
										that.selectedVarAttribs[attrId] = null;
									}
									// remove current selection
									this.selected = false;
								}
							});
						}
					});
					
				},

				enableControl : function( control ) {
					control.removeClass("unselectable disabled");
					var dataa = control.parents(".swatches").data("data");  // data is id set via app.hiddenData api

					if( dataa && dataa=="a1" ) {
						control.unbind(".unavailable");
						// Remove translucent cover to Color badge
/*						var layer = control.next();
						if( layer.hasClass('cover') ) {
							layer.css({'display':'none'});
						}
*/
					}
				},
				disableControl : function( control ) {
					var that = this;
					
					control.addClass("unselectable disabled");
					var dataa = control.parents(".swatches").data("data");  // data is id set via app.hiddenData api
										
					if( dataa && dataa=="a1" ) {
						var title = control.find('a').attr('title');
						var colorAttrDef = that.getAttrByID('a1');
						control.bind("mouseover.unavailable", function(){										
							jQuery('.configurator .color-selection .attribute-error-msg:visible').hide();
							that.showSelectedVarAttrVal("a1", title + " - Currently Unavailable");// changed from innerHTML										
						//	that.showImages(title,colorAttrDef.vals);
						}).bind("mouseleave.unavailable", function(){
							that.showSelectedVarAttrVal("a1", that.selectedVarAttribs['a1']||"");// changed from innerHTML										
						//	that.showImages("", [{val: "", images: model.images}]);
						});
						
						// Add translucent cover to Color badge
/*						var layer = control.next();
						if( layer.hasClass('cover') ) {
							layer.css({'opacity':'0.35','display':'block'});
						} else {
							var width = control.find('a').width();
							var height = control.find('a').height();
							var title = control.find('a').attr('title');
							var colorAttrDef = that.getAttrByID('a1');
							var left = (control.prevAll('li').size())*(width+4+5);
							layer = jQuery('<div />').css({backgroundColor:'#888888', left:left, marginTop:'5px', opacity:'0.35', position:'absolute', width:width+4, height:height+4}).addClass('cover').hover(
								function(){										
									jQuery('.configurator .color-selection .attribute-error-msg:visible').hide();
									that.showSelectedVarAttrVal("a1", title + " - Currently Unavailable");// changed from innerHTML										
									that.showImages(title,colorAttrDef.vals);
								},
								function(){
									that.showSelectedVarAttrVal("a1", that.selectedVarAttribs['a1']||"");// changed from innerHTML										
									that.showImages("", [{val: "", images: model.images}]);
								}
							);
							control.after( layer );
						}
*/					}
				},
				
				refreshView: function() {
					var thisProduct = this;					
					if (!isLoadingVar && this.selectedVar == null) {
						// if we have loaded the variations data then lets if the user has already selected some values
						// find a matching variation
						this.selectedVar = this.findVariation(this.selectedVarAttribs);
					}

					//if( app.ProductCache.variant ) return;
					
					if (!isLoadingVar && this.selectedVar != null) {
						// update availability
						var availMsg = createAvMessage(thisProduct, 1);
						setAvailabilityMsg(availMsg,true,availMsg.indexOf("1-3 Days")>-1?{font:"#689C34",back:"#fff"}:"");
						// update price
						this.showUpdatedPrice(this.selectedVar.pricing.sale, this.selectedVar.pricing.standard);

						if (!(!this.selectedVar.inStock && this.selectedVar.avStatus === app.constants.AVAIL_STATUS_NOT_AVAILABLE)) {
							// enable add to cart button							
							enableAddToCart(thisProduct);
							enableAddToWishlist(thisProduct);
							jQuery(this).trigger("AddtoCartEnabled");
						}
						else {
							disableAddToCart(thisProduct);
							disableAddToWishlist(thisProduct);
						}
					}
					else {
						
						if (isLoadingVar) {
						// update availability
							setAvailabilityMsg(app.showProgress("productloader"));
						}
						else {
							setAvailabilityMsg(app.resources["NON_SELECTED"]);
						}
						// disable add to cart button
						disableAddToCart(thisProduct);
						disableAddToWishlist(thisProduct);
					}
					
					var nonSelectedVars = [];
					
					var validVariants = null;
					
					for (var selectedVar in this.selectedVarAttribs) {
						if (this.selectedVarAttribs[selectedVar]) {
							validVariants = this.findVariations({id: selectedVar, val: this.selectedVarAttribs[selectedVar]}, validVariants);
						}						
					}
						
					if( !validVariants || validVariants.length==0 ) setAvailabilityMsg(app.resources['NOT_AVAILABLE']);
					
					// update selected var attr vals
					jQuery.each(model.variations.attributes, function(){
						
						thisProduct.showSelectedVarAttrVal(this.id, thisProduct.selectedVarAttribs[this.id]);
						
						if (!thisProduct.selectedVarAttribs[this.id] || thisProduct.selectedVarAttribs[this.id] == "" ) {
							nonSelectedVars.push(this.name);
							
							thisProduct.varAttrDisplayHandler(this.id, validVariants);
						} 
					});
					
					// process non-selected vals and show updated tooltip for A2C button as a reminder
					// and show it along availability 
					var tooltipStr = '';
					var nsLen = nonSelectedVars.length;
					if (nsLen == 1 || nsLen == 2) {					
						tooltipStr = nonSelectedVars.join(" & ");
					}
					else {
						for (var i=0; i < nsLen; i++) {
							if (i == nsLen - 2) {
								tooltipStr += nonSelectedVars[i] + " & " + nonSelectedVars[i+1];
								break;
							}
							else {
								tooltipStr += nonSelectedVars[i] + ", ";
							} 
						}
					}					
					
					if (nonSelectedVars.length > 0) {
						var availMsg = jQuery.format(app.resources["MISSING_VAL"], tooltipStr);
						setAvailabilityMsg(availMsg);
						jQuery(thisProduct.containerId+" .addtocartBtn:last").attr("title", availMsg);
					}					
					//this.show();
				},

				filteredVariants: function(ignoreAttr) {
					var validVariants = null;
					
					for (var selectedVar in this.selectedVarAttribs) {
						if (this.selectedVarAttribs[selectedVar]&&selectedVar!=ignoreAttr) {
							validVariants = this.findVariations({id: selectedVar, val: this.selectedVarAttribs[selectedVar]}, validVariants);
						}						
					}

					return validVariants;
				},
				
				showUpdatedPrice: function(sale, standard) {
					standard = standard || 0;
					
					sale = (new Number(sale)).toFixed(2);
					standard = (new Number(standard)).toFixed(2);
					
					var priceHtml = app.currencyCodes[model.pricing.currencyCode] + sale;
					jQuery(this.containerId+' .configurator-totals .label:hidden').show()
					
					if (standard > 0 && standard > sale) {
						jQuery(this.containerId+' .configurator-totals .label:visible').hide()
						// show both prices
						priceHtml = '<span class="standardprice">' + app.currencyCodes[model.pricing.currencyCode] + standard + '</span> <span class="salesprice">' + priceHtml + '</span>';
					}					
					
					var priceContainer = jQuery(this.containerId+" .configurator-totals .value:first").html(priceHtml);
					// containerId contains #, get rid of it before finding the right price div
					//jQuery(this.containerId+" .configurator-totals .price").html(priceHtml);
					if(sale == 0.00) priceContainer.addClass('zero-value');
					else priceContainer.removeClass('zero-value');
				},

				optionSelected: function(e) {
					this.selectedOptions[e.data.id] = e.data.val;
					this.selectedPrice[e.data.id] = e.data.price;

					// update price and show
					this.showUpdatedPrice(computePrice(this), model.pricing.standard);
				},

				getPrice: function() {
					return computePrice(this);
				},

				/*
				* receives 2 or 1 var attrib values and tries to figure out if there is a variant with these values.
				* returns true/false
				*/
				isVariation: function(val1, val2) {
					var variant = null, found = false;
					
					var validVariants = null;
					
					for (var selectedVar in this.selectedVarAttribs) {
						if (this.selectedVarAttribs[selectedVar]) {
							validVariants = this.findVariations({id: selectedVar, val: this.selectedVarAttribs[selectedVar]}, validVariants);
						}						
					}
				
				
					for (var i=0; i<validVariants.length; i++) {
						variant = validVariants[i];
						if (variant.avLevels.NOT_AVAILABLE!=1 && variant.attributes[val1.id] == val1.val && (val2 == undefined || variant.attributes[val2.id] == val2.val)) {
						//if (variant.avStatus.NOT_AVAILABLE!=1 && variant.attributes[val1.id] == val1.val && (val2 == undefined || variant.attributes[val2.id] == val2.val)) {						
							return true;
						}
					}
					
					return false;
				},

				/*
				* find 0 or more variants matching the given attribs object(s)
				* return null or found variants
				*/
				findVariations: function(attr, variants) {
					var foundVariants = new Array();
					variants = variants || model.variations.variants;
					
					var variant = null;
					for (var i = 0; i < variants.length; i++) {
						variant = variants[i];
						if (variant != undefined) {
							if ( ('avLevels' in variant) && ('attributes' in variant) ) {
								if ( (variant.avLevels.NOT_AVAILABLE != 1) && (variant.attributes[attr.id] === attr.val) ) {
										foundVariants.push(variant);
								}
							}
						}
					}
					
					return foundVariants;
				},

				/*
				* find a variant with the given attribs object
				* return null or found variation json
				*/
				findVariation: function(attrs) {
					if (!this.checkAttrs(attrs)) {
						return null;
					}

					var attrToStr = function(attrObj) {
						var result = "";
						jQuery.each(model.variations.attributes, function(){
							result += attrObj[this.id];
						});
						return result;
					}

					var attrsStr = attrToStr(attrs);

					for (var i = 0; i < model.variations.variants.length; i++) {
						variant = model.variations.variants[i];
						if (variant != undefined) {
							if ('attributes' in variant) {
								if (attrToStr(variant.attributes) == attrsStr) {
									return variant;
								}
							}
						}
					}
					return null;
				},

				findVariationById: function(id) {
					for (var i=0; i<model.variations.variants.length; i++) {
						var variation = model.variations.variants[i];
						if (variant != undefined) {
							if ('id' in variation) {
								if (variation && variation.id === id) {
									return variation;
								}
							}
						}
					}

					return {};
				},

				/*
				* see if the specified attrs object has all the variation attributes present in it
				* return true/false
				*/
				checkAttrs: function(attrs) {
					for (var i=0; i<model.variations.attributes.length; i++) {
						if (attrs[model.variations.attributes[i].id] == null) {
							return false;
						}
					}
					return true;
				},
				
				// given an id, return attr definition from model.variations.attributes
				getAttrByID: function(id) {
					for (var i=0; i<model.variations.attributes.length; i++) {
						if (model.variations.attributes[i].id === id) {
							return model.variations.attributes[i];
						}
					}
					return {};
				},
				
				// returns current availability status e.g. in_stock, preorder etc.
				getAvStatus: function() {
					if ((this.variant || this.master) && this.selectedVar != null) {
						return this.selectedVar.avStatus;
					}
					else {
						return model.avStatus;
					}
				},
				
				// return available to sell qty
				getATS: function() {
					if ((this.variant || this.master) && this.selectedVar != null) {
						return this.selectedVar.ATS;
					}
					else {
						return model.ATS;
					}
				},
				
				// returns in stock date 
				getInStockDate: function() {
					if ((this.variant || this.master) && this.selectedVar != null) {
						return this.selectedVar.inStockDate;
					}
					else {
						return model.inStockDate;
					}
				},
				
				// determine if A2C button is enabled or disabled
				// true if enabled, false otherwise
				isA2CEnabled: function() {
					if (this.variant || this.master) {
						if (this.selectedVar != null) {
							return this.selectedVar.avStatus === app.constants.AVAIL_STATUS_IN_STOCK;
						}
						else {
							return false;
						}
					}
					else {
						return model.avStatus === app.constants.AVAIL_STATUS_IN_STOCK;;
					}
				},
				
				show: function(options) {
					// preserve this instance
					var thisProduct = this;

					// bind events
					jQuery(this).bind("VariationsLoaded", {}, function(e, source){
						if (thisProduct.variant && thisProduct.selectedVar == null) {
							thisProduct.selectedVar = thisProduct.findVariationById(thisProduct.pid);
							thisProduct.selectedVarAttribs = thisProduct.selectedVar == null ? {} : jQuery.extend({}, {}, thisProduct.selectedVar.attributes);
						}
						
						// enable/disable unavailable values
						if (source && source == "loadVariants") {
							thisProduct.resetVariations();
						}
						thisProduct.refreshView();
					});

					this.containerId 	= "#"+options.containerId;
					var container		= jQuery(this.containerId);
					var append			= options.append;
					var isQuickView 	= false;

					if (options.source && options.source == "quickview") {
						isQuickView = true;
					}

					if (append) {
						this.containerId = "#"+this.pid+"Div";
					}
					myContainerId = this.containerId;
					
					// bind click handlers for prev/next links
					getNavLinks();
					
					// size chart click binding
					getSizeChart();

					// variation attributes
					if (model.master || model.variant) {
						loadVariants(this); // make a server call to load the variants, this is due to the performance reasons
						// meanwhile display the available variation attributes
						jQuery.each(model.variations.attributes, function(){

							var thisAttr = this;

							thisProduct.varAttributes[this.id] = {};

							var pdpVarId = this.id;
//							var singleValue = jQuery(thisProduct.containerId + " #pdpVarAttrDiv input#pdp"+pdpVarId+"var");
							
							if( false ) { //singleValue.size()==1 ) {
								
								var e = {'data' : { 'id' : pdpVarId, 'val':singleValue.val() } };
								thisProduct.varAttrSelected(e);								
						   /*	} else if( this.ui == 0 ) {
								var ele = jQuery(thisProduct.containerId + " #pdp"+this.id+"var")
								
								if(ele[0] && ele[0].selectedIndex >= 0 && ele[0].options[ele[0].selectedIndex].value != "") {
									// grab the currently selected val									
									thisProduct.selectedVarAttribs[this.id] = ele[0].options[ele[0].selectedIndex].value;
								}
								
								// default ui i.e. drop down
								if( ele ) {
									ele.data("data", {id: this.id, val: ''}).change(function(e){
										if (this.selectedIndex == 0) { return; }

										e.data = jQuery(this).data("data");
										e.data.val = this.options[this.selectedIndex].value;
										thisProduct.varAttrSelected(e);
									});
								}
							*/
							} else {
								// color, width, size
								// its a custom ui with div controlled via css
								var pdpVarId = this.id;
								
								// grab the currently selected attr val
								thisProduct.selectedVarAttribs[pdpVarId] = jQuery("#pdp"+pdpVarId+"var .selected a").attr("title");								
								
								var varEventHandler = function(e){
									var thisObj = jQuery(this);
									
									var val = $(this).attr("title");
									
									if (thisObj.parent().hasClass("unselectable")) {
										return false;
									}
									else if (thisObj.parent().hasClass("selected")) {
										// deselection
										e.data = {id: pdpVarId, val: null};
										thisObj.parent().removeClass("selected");
										// clear the current selection
										thisProduct.resetVariations();
										thisProduct.varAttrSelected(e);
									}
									else {
										var parRow = thisObj.parent();
										e.data = {id: pdpVarId, val: val};
										parRow.siblings().removeClass("selected");
										parRow.addClass("selected");
										while(parRow.parent().length && !parRow.hasClass('row')) parRow=parRow.parent();
										parRow.find('.attribute-error-msg:visible:first').stop().fadeOut(500);
										thisProduct.varAttrSelected(e);
									}
									return false;
								}
								
								var varJqryObjs = jQuery("#pdp"+pdpVarId+"var a");
								// if its a color attr then render its swatches
								var colorVal = '';
								if (pdpVarId === "a1") {
									var colorAttrDef = thisProduct.getAttrByID('a1');
									varJqryObjs.each(function(){
									
										// given a variation attr value, find its swatch image url
										var findSwatch = function(val) {
											for (var i=0; i<colorAttrDef.vals.length; i++){
												if (colorAttrDef.vals[i].val === val) {													
													return colorAttrDef.vals[i].images.swatch;
												}
											}
											return ""; // no swatch image found
										}
										
										// PJP: Changed to use the title, not innerHTML
										colorVal = jQuery(this).attr("title");
										var swatchUrl = findSwatch(colorVal); // find swatch url 
										
										if (swatchUrl && swatchUrl != "") {
											jQuery(this).find("span:first-child").css("background", "url(" + swatchUrl + ")");
										}
										else {
											//jQuery(this).css("color", "transparent"); // no swatch image found
										}
									});
									
									varJqryObjs.data("data", {id: pdpVarId}).click(varEventHandler).mouseenter(function(e){
										var colorVal = $(this).attr("title");										
										thisProduct.showSelectedVarAttrVal("a1", colorVal);// changed from innerHTML
										//thisProduct.showImages(colorVal, colorAttrDef.vals);// changed from innerHTML
										jQuery('.configurator .color-selection .attribute-error-msg:visible').hide();
									}).mouseleave(function(e) {
										if (thisProduct.selectedVarAttribs["a1"]) {
										//	thisProduct.showImages(thisProduct.selectedVarAttribs["a1"], colorAttrDef.vals)
										}
										else {
										//	thisProduct.showImages("", [{val: "", images: model.images}]);
										}
										
										thisProduct.showSelectedVarAttrVal("a1", thisProduct.selectedVarAttribs["a1"] || "");
									});
								}
								else {								
									varJqryObjs.data("data", {id: pdpVarId}).click(varEventHandler);
								}
							}
							
							//set selected 
							
							
						});
						
						if (thisProduct.selectedVarAttribs["a1"]) {
							// show swatch related images for the current value								
							thisProduct.showImages(thisProduct.selectedVarAttribs["a1"], thisProduct.getAttrByID('a1').vals);
						}
						else {
							// show images and bind hover event handlers for small/thumbnails to toggle large image								
							thisProduct.showImages("", [{val: "", images: model.images}]);
						}
					}
					else {
						// show images and bind hover event handlers for small/thumbnails to toggle large image								
						thisProduct.showImages("", [{val: "", images: model.images}]);
					}
					
					// bind product options event(s)
					getOptionsDiv(this);

					if(!model.productSet) {
						// quantity box
						if (!model.bundle) {
							getQtyBox(this);
						}// update avaiability for a bundle product, for everything else its done inside getQtyBox
						else if (model.bundle) {
							setAvailabilityMsg(createAvMessage(this, 1));
						}
					}

					// Add to cart button
					var addToCartBtn = getAddToCartBtn(this);
					if (model.master || model.productSet || model.bundle || (!model.inStock && model.avStatus === app.constants.AVAIL_STATUS_NOT_AVAILABLE && !model.productSet)) {
						//jQuery("div.addtocart button").attr("disabled", "disabled");
						jQuery("div.addtocart").addClass("disabled");
					}
																			
					if (model.bundle) {
						// if the bundled products are standard prodcuts then determine disability of the a2c button by checking each producgt's availability
						var bundleA2CEnabled = false;
						for (var i = 0; i < thisProduct.subProducts.length; i++) {
							var subProduct = thisProduct.subProducts[i];
							bundleA2CEnabled = subProduct.isA2CEnabled();
							if (!bundleA2CEnabled) {
								break;
							}
						}
						if (!bundleA2CEnabled) {
							disableAddToCart(thisProduct);
							disableAddToWishlist(thisProduct);
						} 
						else {
							enableAddToCart(thisProduct);
							enableAddToWishlist(thisProduct);
						}
					}						

					if (!model.subProduct && !model.bundled) {

						if (!model.productSet && !isQuickView && !model.bundle) {
							// customer rating
							getRatingSection(this.containerId);							
						}
					}
					
					// wish list, sent to friend, add to gift
					getMiscLinks(this);
							
					// recommendations carosel
					loadRecommendations(this.containerId);

					// tabs
					getTabs(this.containerId);										
					
					// see if have any subproducts and bind AddtoCartEnabled event
					jQuery.each(thisProduct.subProducts, function(){
						jQuery(this).bind("AddtoCartEnabled", {},
							/**
							* Event handler when a subproduct of a product set or a bundle is selected.
							* Basically enable the add to cart button or do other screen refresh if needed like price etc.
							*/
							function() {
								// enable Add to cart button if all the sub products have been selected
								var enableAdd2Cart = true;
								var subProducts = thisProduct.subProducts;
								var price = new Number();

								for (var i = 0; i < subProducts.length; i++) {
									if (((subProducts[i].variant || subProducts[i].master) && subProducts[i].selectedVar == null) ||
										(!subProducts[i].bundled && (subProducts[i].selectedOptions["Quantity"] == undefined ||
										subProducts[i].selectedOptions["Quantity"] <= 0))) {
										enableAdd2Cart = false;
										break
									}
									else {
										if (subProducts[i].selectedVar != null) {
											subProducts[i].selectedOptions.pid = subProducts[i].selectedVar.pid;
										}
										else {
											subProducts[i].selectedOptions.pid = subProducts[i].pid;
										}

										price = price + new Number(subProducts[i].getPrice());
									}
								}

								if (enableAdd2Cart && (model.productSet || model.inStock)) {
									enableAddToCart(thisProduct);
									enableAddToWishlist(thisProduct);
									// show total price
									thisProduct.showUpdatedPrice(price);
								} else {
									disableAddToCart(thisProduct);
									disableAddToWishlist(thisProduct);
								}
							}
						);
					});
				},

				toString: function() {
					return this.model;
				}
			}
		} // Product defintion end
	}
	else {
		// dw namespace has not been defined yet
		alert("app namespace is not loaded yet!");
	}
})(app);

