// product_page.js - required for perspectives and product selection

// IMPORTANT: the mouse handlers in this lib (*Clicked(), *Moused*()) are
// disabled until the product page finishes loading, as determined by the
// global boolean var PP_initialized. Otherwise Javascript throws all kinds
// of exceptions and the page crashes at it tries to handle user interactions
// with half-loaded data.

function getProductForm() {
	return document.getElementById('skusetForm');
}

// Record an action with Google Analytics, if available. Otherwise do nothing.
function recordAction(url) {
	if(window.urchinTracker) {
		try {
			window.urchinTracker(url);
		} catch(e) {}
	}
}

// A utility for switching button statuses according to a lookup table of 
// mutually exclusive CSS classes, one per status. Requires yui-dom utils.
// 	newStatus: an int index into statuses[]
// 	statuses[]: a table of css classes.
// 	element: is granted the class indexed by newStatus and stripped of the other classes in the table. 
function setStatusMutex(element,newStatus,statuses) {
	if(element != null && newStatus != null && statuses != null) {
		if(newStatus >= statuses.length) {
			var err = "Bad status: " + newStatus + " in " + statuses;
			throw err;
		} else {
			for(var i=0; i<statuses.length; i++) {
				if(i != newStatus) {
					YAHOO.util.Dom.removeClass(element,statuses[i]);
				}
			}
			if(!YAHOO.util.Dom.hasClass(element,statuses[newStatus])) {
				YAHOO.util.Dom.addClass(element,statuses[newStatus]);
			}
		}
	}
}

function updateScrollButtonStatus(isLeftButton,isMouseOver) {
	var status = 0;
	var token = isLeftButton ? 'left' : 'right';
	var enabled = isMouseOver ? 2 : 1;
	if(isLeftButton) {
		if(perspectiveButtonScrollOffset <= -1.0) {
			status = enabled;
		}
	} else if(canScrollPerspectiveButtonsFurtherRight()) {
		status = enabled;
	} 
	setScrollButtonStatus(token,status);
}

function scrollLeftButtonMousedOver() {
	if(!PP_initialized) return;
	updateScrollButtonStatus(true,true);
}

function scrollLeftButtonMousedOut() {
	if(!PP_initialized) return;
	updateScrollButtonStatus(true,false);
}

function scrollRightButtonMousedOver() {
	if(!PP_initialized) return;
	updateScrollButtonStatus(false,true);
}

function scrollRightButtonMousedOut() {
	if(!PP_initialized) return;
	updateScrollButtonStatus(false,false);
}

function scrollLeftButtonClicked() {
	if(!PP_initialized) return;
	scrollPerspectiveButtons(getPerspectiveButtonWidth());
}

function scrollRightButtonClicked() {
	if(!PP_initialized) return;
	scrollPerspectiveButtons(-1.0 * getPerspectiveButtonWidth());
}

function getPerspectiveButtonWidth() {
	return 54.0;
}

function updatePerspectiveScrollButtons() {
	var display = "none";
	var scrollable = false;
	if(getScrollableButtonCount() > scrollAreaMaxButtonCount) {
		display = "block";
		scrollable = true;
	}
	updateScrollButtonStatus(true,false);
	updateScrollButtonStatus(false,false);
	document.getElementById('perspective_scroll_left_button').style.display=display;
	document.getElementById('perspective_scroll_right_button').style.display=display;

	var clip = document.getElementById('perspective_clip_rect');

	// If needed, reconfigure the cliprect to fit the maximum available area.
	if(YAHOO.util.Dom.hasClass(clip,'PP-perspective-clip-rect')) { // product page
		var nonscrollableCSS = 'PP-perspective-clip-rect-nonscrollable';
		if(scrollable) {
			if(YAHOO.util.Dom.hasClass(clip,nonscrollableCSS)) {
				YAHOO.util.Dom.removeClass(clip,nonscrollableCSS);
			}
		} else {
			if(!YAHOO.util.Dom.hasClass(clip,nonscrollableCSS)) { // nonscrollable
				YAHOO.util.Dom.addClass(clip,nonscrollableCSS);
			}
		}
	} else { // magnifier popup
		var nonscrollableCSS = 'PP-perspective-clip-rect-nonscrollable-magnifier';
		if(scrollable) {
			if(YAHOO.util.Dom.hasClass(clip,nonscrollableCSS)) {
				YAHOO.util.Dom.removeClass(clip,nonscrollableCSS);
			}
		} else if(!YAHOO.util.Dom.hasClass(clip,nonscrollableCSS)) { // nonscrollable
			YAHOO.util.Dom.addClass(clip,nonscrollableCSS);
		}
	}
}

function getScrollableButtonCount() {
	var count = 0;
	var cp = getSelectedColorwayDatum();
	if(cp != null) {
		for(var i=0; i<perspectiveTokens.length; i++) {
			var p = perspectiveTokens[i];
			if(cp.enabledPerspectives[p]) {
				count++;
			}
		}
	}
	return count;
}

function canScrollPerspectiveButtonsFurtherRight() {
	var count = getScrollableButtonCount();
	if(count <= scrollAreaMaxButtonCount) return false;

	// To scroll right, we translate left with negative values.
	var too_far = -1.0 * (count - scrollAreaMaxButtonCount) * getPerspectiveButtonWidth();
	return (perspectiveButtonScrollOffset >= too_far); // remember, too far is a negative offset
}

/* Use YUI relative offset scrolling to move the cropped tray of perspective
 * buttons within their cliprect. Because this relies on relative positioning,
 * and because IE7 and earlier have known bugs that prevent overflow: hidden
 * boxes from cropping their position:relative children, we are forced to 
 * supply a deprecated implementation to keep Microsoft's flagship afloat.
 * See scrollPerspectiveButtons_IE7(..).
 *
 * param xoffset: 
 *  A positive value translates content right (scrolls left).
 *  A negative value translates content left (scrolls right).
 */
function scrollPerspectiveButtons(xoffset) {
	if (YAHOO.env.ua.ie > 0 && YAHOO.env.ua.ie < 8) {
		scrollPerspectiveButtons_IE7(xoffset);
	} else {
		var xtotal = xoffset + perspectiveButtonScrollOffset;
		if(xtotal > 0.0) {
			xoffset = perspectiveButtonScrollOffset;
		} else if(xoffset < 0.0 && !canScrollPerspectiveButtonsFurtherRight()) {
			// You're trying to scroll too far.
			return;
		} else if(xoffset != 0.0) {
			var pos = YAHOO.util.Dom.getXY('perspective_scroll_content');
			pos[0] += xoffset;
			YAHOO.util.Dom.setXY('perspective_scroll_content',pos);
			perspectiveButtonScrollOffset += xoffset;

			updateScrollButtonStatus(true,(xoffset > 0.0));
			updateScrollButtonStatus(false,(xoffset < 0.0));
		}
	}
}

/* Scroll by hiding or revealing perspective buttons. Required for old IE.
 * See IE7 position:relative + overflow:hidden bug notes for 
 * scrollPerspectiveButtons(xoffset). If offset is 0, recalculate visibility
 * (apparent scroll) for all available perspective buttons.
 */
function scrollPerspectiveButtons_IE7(xoffset) {
	if(xoffset < 0.0 && !canScrollPerspectiveButtonsFurtherRight()) {
		// you're trying to scroll too far.
		return;
	} 
	var cp = getSelectedColorwayDatum();
	if(cp != null) {
		var xtotal = xoffset + perspectiveButtonScrollOffset;
		var units_net_scrolled = -1 * (xtotal / 54);
		if(units_net_scrolled < 0) {
			return; // you can't scroll any farther
		}
		var units_hidden_sum = 0;

		for(var i=0; i<perspectiveTokens.length; i++) {
			var p = perspectiveTokens[i];
			var perspectiveImageButtonId = getPerspectiveImageButtonId(i);
			var perspectiveImageContainerId = getPerspectiveImageContainerId(i);

			var shouldHidePerspective = false;
			if(cp.enabledPerspectives[p]) {
				if(units_hidden_sum < units_net_scrolled) { // has it scrolled offscreen?
					shouldHidePerspective = true;
					units_hidden_sum++;
				}
			} else {
				shouldHidePerspective = true;
			}

			conditionallySetElementDisplayStyle(perspectiveImageContainerId,!shouldHidePerspective,'block','none');
			conditionallySetElementSrc(perspectiveImageButtonId,!shouldHidePerspective,cp.getPerspectiveButtonFilename(p),'/static/widgets/1x1_white.gif');
		}

		perspectiveButtonScrollOffset += xoffset;

		updateScrollButtonStatus(true,(xoffset > 0.0));
		updateScrollButtonStatus(false,(xoffset < 0.0));
	}
}

function setScrollButtonStatus(token,status) { // token is 'left' or 'right'
	var id = 'perspective_scroll_' + token + '_button';
	var button = document.getElementById(id);
	replaceScrollButtonStatusClasses(button,status);
}

function replaceScrollButtonStatusClasses(element,newStatus) {
	var DISABLED = "PP-perspective-scroll-button-disabled";
	var ENABLED  = "PP-perspective-scroll-button-enabled";
	var OVER     = "PP-perspective-scroll-button-over";
	setStatusMutex(element,newStatus,[DISABLED,ENABLED,OVER]);
}

// Sets the main image for the currently selected colorway.
// Doesn't update perspective buttons.
function redrawMainPerspectiveImage() {
	var cp = getSelectedColorwayDatum();
	if(cp === null || cp === undefined) {
		var err = "Dud cp in redrawMainPerspectiveImage: " + cp;
		throw err;
	} else {
		var id = "mainPerspectiveImage";
		var img = document.getElementById(id);
		if(img === null || img === undefined) {
			var err = "Dud img at " + id + " in redrawMainPerspectiveImage: " + img
			throw err;
		}else {
			var url = cp.getPerspectiveFilename(perspectivePathToken);
			if(url === null || url === undefined) {
				var err = "Dud URL in redrawMainPerspectiveImage: " + url
				throw err;
			} else {
				img.src=url;
			}
		}
	}
}

// Draw the specified perspective's image onscreen. Called when a user clicks a perspective button.
function swapPerspectiveImageForPerspective(pPathToken) {
	perspectivePathToken = pPathToken;
	redrawMainPerspectiveImage();
}

function conditionallySetElementSrc(elementId,condition,trueSrc,falseSrc) {
	var element = document.getElementById(elementId);
	if(element !== null && element !== undefined) {
		if(condition==true) {
			element.src=trueSrc;
		} else {
			element.src=falseSrc;
		}
	}
}

function conditionallySetElementDisplayStyle(elementId,condition,trueDisplayStyle,falseDisplayStyle) {
	var element = document.getElementById(elementId);
	if(element !== null && element !== undefined) {
		if(condition==true) {
			element.style.display=trueDisplayStyle;
		} else {
			element.style.display=falseDisplayStyle;
		}
	}
}

function validateFormStructure() {
	var err = null;
	if(document === null || document === undefined) {
		err = "document " + document;
	} else if(document.getElementById('skusetForm') === null || document.getElementById('skusetForm') === undefined) {
		err ="document.getElementById('skusetForm') is " + document.getElementById('skusetForm');
	} else {
		var sf = document.getElementById('skusetForm');
		var x = sf.sizeOptIdx;
		if(x === null || x === undefined) {
			err = "skusetForm.sizeOptIdx is " + x;
		} else {
			var y = sf.colorwayOptIdx;
			if(y === null || y === undefined) {
				err = "skusetForm.colorwayOptIdx is " + y;
			} else {
				var smField = sf.shoppingMode;
				if(smField === null || smField === undefined) {
					err = "skusetForm.shoppingMode is " + smField;
				} else {
					var sm = smField.value;
					if(sm != "size" && sm != "color") {
						err = "skusetForm.shoppingMode.value is " + sm;
					}
				}
			}
		}
	}
	if(err !== null) throw err;
}

function getPerspectiveImageButtonId(index) {
	return "perspective_img_button_" + perspectiveTokens[index];
}

function getPerspectiveImageContainerId(index) {
	return "perspective_img_button_container_" + perspectiveTokens[index];
}

/* Hide perspective buttons that are not valid for the selected
 * colorway, and show the buttons that are valid.
 * Called by selectColorId(cwId), and also called upon pageLoad at the
 * button of skuset.jsp (in case the lowest-idx colorway contains invalid
 * perspectives). Also updates the visibility of customizeButton.
 */
function updatePerspectiveButtons() {
	var cp = getSelectedColorwayDatum();
	if(cp != null) {
		updatePerspectiveScrollButtons();
		
		if (YAHOO.env.ua.ie > 0 && YAHOO.env.ua.ie < 8) {
			scrollPerspectiveButtons_IE7(0); //TEMP09
		} else {
			for(var i=0; i<perspectiveTokens.length; i++) {
				var p = perspectiveTokens[i];

				var perspectiveImageButtonId = getPerspectiveImageButtonId(i);
				var perspectiveImageContainerId = getPerspectiveImageContainerId(i);

				conditionallySetElementDisplayStyle(perspectiveImageContainerId,cp.enabledPerspectives[p],'block','none');
				conditionallySetElementSrc(perspectiveImageButtonId,cp.enabledPerspectives[p],cp.getPerspectiveButtonFilename(p),'/static/widgets/1x1_white.gif');
			}
		}

		var currentPerspectiveIsValid = (cp.enabledPerspectives[perspectivePathToken]==true);
		if(!currentPerspectiveIsValid) {
			swapPerspectiveImageForPerspective('f');
		}

		/* Customization prompt temporarily disabled:
			var customizeButton = document.getElementById("customizeThisBagButton");
			if(customizeButton != null && customizeButton != undefined) {
				var customizeLink = document.getElementById("customizeThisBagLink");
				if(customizeLink != null && customizeLink != undefined) {
					var bbUrl = bagbuilderURL;
					if(cp.customizable) {
						customizeButton.style.display='block';
						bbUrl=bagbuilderURL + '?productId=' + cp.productId;
					} else {
						customizeButton.style.display='none';
					}
					customizeLink.href=bbUrl;
				} else {
					customizeButton.style.display='none';
				}
			}
		*/
		conditionallySetElementDisplayStyle("magnifier_open_div",cp.magnificationEnabled,'block','none');
	}
}

// For perspectives.
function getSelectedProductOptionIndex() {
	var x = getSelectedSizeIdx();
	var y = getSelectedColorwayIdx();
	return prodMatrix[x][y];
}

// Return the ColorwayPerspectiveDatum object for the currently visible product.
function getSelectedColorwayDatum() {
	var cwIdx = getSelectedProductOptionIndex();
	var cp = colorwayPerspectiveData[cwIdx];
	return cp;
}

function openMagnifier() {
	var cp = getSelectedColorwayDatum();
	var productId = cp.productId;
	var url = "/tb2/magnifier.htm?productId=" + productId;
	var win = window.open(url,'magnifier','height=630,width=585','status=0,toolbar=0,directories=0,menubar=0,location=0');
	if(win) {
		win.moveTo(0,0);
		win.focus();
	}
}

function openMagnifierConditionally() {
	if(getSelectedColorwayDatum().magnificationEnabled) {
		openMagnifier();
	}
}

function cartButtonClicked() {
	getProductForm().submit();
}

function SizeOpt(key,src,disabledSrc,name) {
	this.id = key;
	this.buttonSrc = src;
	this.disabledButtonSrc = disabledSrc;
	this.name = name;
}

function ColorwayOpt(key,src,disabledSrc,namesArray) {
	this.id=key;
	this.buttonSrc = src;
	this.disabledButtonSrc = disabledSrc;
	this.names=namesArray;
}

function ProductOpt(key,sizeOption,colorwayOption,price,priceFormatted,priceMSRP,priceMSRPFormatted,sku,tagline) {
	this.id=key;
	this.sizeOption=sizeOption;
	this.colorwayOption=colorwayOption;
	this.price=price;
	this.priceFormatted=priceFormatted;
	this.priceMSRPFormatted=priceMSRPFormatted;
	this.priceMSRP=priceMSRP;
	this.sku = sku;
	this.tagline=tagline;
}

// Param 'mode' is true for size, false for color.
// See notes in productSelectorFormFields.jsp!
function setShopBySizeMode(mode) {
	var sm = "size";
	if(mode == false) {
		sm = "color";
	}
	getProductForm().shoppingMode.value = sm;
}

// return true for size, false for color
function isShopBySizeMode() {
	sm = getProductForm().shoppingMode.value;
	if(sm == "color") {
		return false;
	} else {
		return true;
	}
}

function setSelectedSizeIdx(x) {
	getProductForm().sizeOptIdx.value = x;
}

function getSelectedSizeIdx() {
	return getProductForm().sizeOptIdx.value;
}

function getSelectedSizeOpt() {
	return sizeOpts[getSelectedSizeIdx()];
}

function setSelectedColorwayIdx(y) {
	getProductForm().colorwayOptIdx.value = y;
}

function getSelectedColorwayIdx() {
	return getProductForm().colorwayOptIdx.value;
}

function getSelectedColorwayOpt() {
	return cwOpts[getSelectedColorwayIdx()];
}

function getSelectedProductOpt() {
	var err = null;
	var x = getSelectedSizeIdx();
	var y = getSelectedColorwayIdx();
	var prodOpt = null;

	if(prodMatrix === null || prodMatrix === undefined) {
		err = "Error. getSelectedProductOpt(): prodMatrix is " + prodMatrix;
	} else if(prodMatrix.length <= x) {
		err = "Error. getSelectedProductOpt(): prodMatrix.length is " + prodMatrix.length + " <= " + x;
	} else if(prodMatrix[x].length < y) {
		err = "Error. getSelectedProductOpt(): prodMatrix["+x+"].length is " + prodMatrix[x].length + " <= " + y;
	} else {
		if(prodOpts === null || prodOpts === undefined) {
			err = "Error. getSelectedProductOpt(): prodOpts is " + prodOpts;
		} else {
			prodIdx = prodMatrix[x][y];
			if(prodIdx === null || prodIdx === undefined || prodIdx < 0 || prodIdx > prodOpts.length) {
				err = "Error. getSelectedProductOpt(): prodMatrix["+x+"]["+y+"] is " + prodIdx;
			} else {
				prodOpt = prodOpts[prodIdx];
				if(prodOpt === null || prodOpt === undefined) {
					err = "Error. getSelectedProductOpt(): prodOpts["+prodIdx+"] is " + prodOpt;
				}
			}
		}
	}
	if(err !== null) throw err;
	return prodOpt;
}

function synchMagnifierView() {
	updatePerspectiveButtons();
	redrawMainPerspectiveImage();
}

function synchSkusetView() {
	//If you are having problems, enable validation:
	//validateFormStructure();

	var x = getSelectedSizeIdx();
	var y = getSelectedColorwayIdx();

	var prod = getSelectedProductOpt();
	if(prod !== null && prod !== undefined) {
		synchPrice(prod,true);
		synchTagline(prod);
		synchSku(prod);
		synchTotal(prod);

		synchSizeText(x);
		synchColorwayText(y);

		var shopBySize = isShopBySizeMode();
		synchShoppingModeTab(shopBySize);
		synchColorwayStatus(x,shopBySize);
		synchSizeStatus(y,shopBySize);
	}

	updatePerspectiveButtons();
	redrawMainPerspectiveImage();
}

function getTagline(prodOpt) { // If product has no tagline, fall back on the skuset.
	var tagline = prodOpt.tagline;
	if(tagline == null) {
		tagline = defaultTagline;
	}
	return tagline;
}

function synchTagline(prodOpt) {
	var field = document.getElementById('tagline_text')
	if(field === null || field === undefined) {
		var err = "Error. synchTagline(p): document.getElementById('tagline_text') is " + field;
		throw err;
	} else {
		field.innerHTML = getTagline(prodOpt);
	}
}

function synchPrice(prodOpt) {
	var priceLeft = document.getElementById('price_left');
	var priceRight= document.getElementById('price_right');

	if(prodOpt.priceMSRP > prodOpt.price) {
		// It's on sale.
		priceLeft.innerHTML = "$ " + prodOpt.priceMSRPFormatted;
		priceRight.innerHTML = "$ " + prodOpt.priceFormatted;
		setPriceStatus(priceLeft,2);
		setPriceStatus(priceRight,3);
	} else {
		priceLeft.innerHTML = "$ " + prodOpt.priceFormatted;
		priceRight.innerHTML = "";
		setPriceStatus(priceLeft,1);
		setPriceStatus(priceRight,0);
	}
}

function setPriceStatus(element,newStatus) {
	var hidden = "PP-price-text-hidden"; // 0=hidden
	var normal = "PP-price-text-normal"; // 1=normal (black)
	var msrp   = "PP-price-text-msrp";   // 2=MSRP (strikethrough)
	var sale   = "PP-price-text-sale";   // 3=sale (red)
	setStatusMutex(element,newStatus,[hidden,normal,msrp,sale]);
}

// Convert a string or a number to a dollar figure with currency symbol.
function formatDollars(p) { 
	p = eval(p);
	return "$" + p.toFixed(2);
}

function quantitySelectionChanged() {
	prodOpt = getSelectedProductOpt();
	if(prodOpt != null) {
		synchTotal(prodOpt);
	}
}

function synchTotal(prodOpt) {
	var qtyMenu = document.getElementById('PP_quantity_menu');
	var totalField = document.getElementById('PP_total_text');
	if(qtyMenu != null) {
		var qty = qtyMenu.value;
		var price = prodOpt.price;
		var total = price * qty;
		totalField.innerHTML = formatDollars(total);
	}
}

function synchSku(prodOpt) {
	var field = document.getElementById('sku_text')
	if(field === null || field === undefined) {
		var err = "Error. synchTagline(p): document.getElementById('sku_text') is " + field;
		throw err;
	} else {
		field.innerHTML = "STYLE " + prodOpt.sku;
	}
}

function synchProductFormField() {
	var prod = getSelectedProductOpt();
	if(prod === null || prod === undefined) {
		var err = "Error. synchProductFormField(): getSelectedProductOpt() returned " + prod;
		throw err;
	} else {
		getProductForm().productId.value = prod.id; // for submission
	}
}

function sizeButtonMousedOver(x) {
	if(!PP_initialized) return;
	var status = getSizeStatus(x);
	if(status < 2) { // if not already selected
		if(status == 0) {
			fadeSizeButton(x,true); // temporary fade-in
		}
		synchSizeText(x,true);
	}
	synchColorwayStatus(x,true);
}

function sizeButtonMousedOut(x) {
	if(!PP_initialized) return;
	var x1 = getSelectedSizeIdx();
	var status = getSizeStatus(x);
	if(status < 2) {
		if(status == 0) {
			fadeSizeButton(x,false);
		}
		synchSizeText(x1);
	}
	synchColorwayStatus(x1,isShopBySizeMode());
}

function colorwayButtonMousedOver(y) {
	if(!PP_initialized) return;
	var status = getColorwayStatus(y);
	if(status < 2) { // if not already selected
		if(status == 0) { // temporary fade-in
			fadeColorwayButton(y,true);
		}
		synchColorwayText(y);
	}
	synchSizeStatus(y,false);
}

function colorwayButtonMousedOut(y) {
	if(!PP_initialized) return;
	var y1 = getSelectedColorwayIdx();
	var status = getColorwayStatus(y);
	if(status < 2) {
		if(status == 0) {
			fadeColorwayButton(y,false);
		}
		synchColorwayText(y1);
	}
	synchSizeStatus(y1,isShopBySizeMode());
}

function sizeButtonClicked(x) {
	if(!PP_initialized) return;
	if(getSizeStatus(x) < 2) {
		setSelectedSizeIdx(x);

		// If the selected colorway is unavailable in the selected size,
		// search for the default colorway and select it.
		var y = getSelectedColorwayIdx();
		var ok = false;
		if(prodMatrix[x][y] != null) {
			ok = true;
		} else {
			for(y=0; y<prodMatrix[x].length; y++) {
				if(prodMatrix[x][y] !== null) {
					setSelectedColorwayIdx(y);
					ok = true;
					break;
				}
			}
		}
		if(!ok) throw "sizing error";
		setShopBySizeMode(true);
		synchProductFormField();
		synchSkusetView();
		synchBrowserHistory();
	}
}

function colorwayButtonClicked(y) {
	if(!PP_initialized) return;
	if(getColorwayStatus(y) < 2) {
		setSelectedColorwayIdx(y);

		var x = getSelectedSizeIdx();
		var ok = false;
		if(prodMatrix[x][y] != null) {
			// The selected colorway is available in this size.
			ok = true;
		} else {
			var old_size_id = sizeOpts[x].id;
			var nearest_x = null;
			for(x=0; x<prodMatrix.length; x++) {
				// If the selected colorway is out of stock in
				// this size, find the closest available size.
				if(prodMatrix[x][y] != null) {
					if(nearest_x == null) {
						nearest_x = x;
					} else {
						var delta_0 = Math.abs(old_size_id - sizeOpts[nearest_x].id); // current nearest size
						var delta_1 = Math.abs(old_size_id - sizeOpts[x].id); // the current size candidate
						if(delta_0 >= delta_1) { // prefer larger sizes if there are two solutions
							nearest_x = x;
						}
					}
				}
			}
			if(nearest_x != null) {
				x = nearest_x;
				if(prodMatrix[x][y] != null) {
					setSelectedSizeIdx(nearest_x);
					ok = true;
				}
			}
		}
		if(!ok) throw "colorway error";
		synchProductFormField();
		synchSkusetView();
		synchBrowserHistory();
	} 
}

function shoppingModeButtonClicked(mode) {
	if(!PP_initialized) return;
	if(mode != isShopBySizeMode()) {
		setShopBySizeMode(mode);
		synchSkusetView();
		var shopModeUrl = "/tb2/products/shop_by_" + getProductForm().shoppingMode.value + "_clicked";
		recordAction(shopModeUrl);
	}
}

// buttonForMode: true=size, false=color
function shoppingModeButtonMousedOver(buttonForMode) {
	if(!PP_initialized) return;
	if(buttonForMode != isShopBySizeMode()) {
		var status = (buttonForMode == true) ? [2,0] : [0,2];
		setShoppingModeTabStatuses(status[0],status[1]);
	}
}

// buttonForMode: true=size, false=color
function shoppingModeButtonMousedOut(buttonForMode) {
	if(!PP_initialized) return;
	if(buttonForMode != isShopBySizeMode()) {
		var status = (buttonForMode == true) ? [1,0] : [0,1];
		setShoppingModeTabStatuses(status[0],status[1]);
	}
}

// Status: 0=enabled (inactive), 1=disabled (active), 2=hover (inactive)
function setShoppingModeTabStatuses(sizeTabStatus,colorTabStatus) {

	// compute graphic separator status as a function of hover and mode
	var seamStatus = null;
	if(sizeTabStatus == 2) {
		seamStatus = 3;
	} else if(colorTabStatus == 2) {
		seamStatus = 2;
	} else {
		seamStatus = sizeTabStatus;
	}

	var sizeTab = document.getElementById('PP_shop_by_size_button');
	var seam = document.getElementById('PP_shopping_mode_tab_seam'); // graphical separator
	var colorTab = document.getElementById('PP_shop_by_color_button');

	if(sizeTab !== null && sizeTab !== undefined 
		&& seam !== null && seam !== undefined
		&& colorTab !== null && colorTab !== undefined)
	{
		// sometimes we don't show these tabs
		setShoppingModeTabStatus(sizeTab,sizeTabStatus);
		setShoppingModeTabSeamStatus(seam,seamStatus);
		setShoppingModeTabStatus(colorTab,colorTabStatus);
	}
}

function synchShoppingModeTab(shopBySize) { // True for size, false for color.
	var sizeTabStatus = 0; // 0=disabled: you can't click it, because it's the current mode
	var colorTabStatus= 1; // 1=enabled: you can clickit, because it's not the current mode
	if(!shopBySize) {
		sizeTabStatus = 1;
		colorTabStatus = 0;
	}

	setShoppingModeTabStatuses(sizeTabStatus,colorTabStatus);
}

// x: size option index for the size we want to label
// showPrice: optional arg, boolean or null. if true, append a price range to the label.
function synchSizeText(x,showPrice) {
	var label = sizeOpts[x].name;
	document.getElementById('size_text').innerHTML=label;
	var sizePriceLabel = document.getElementById('size_price_label');
	var sizePriceLabelText="";
	if(true == showPrice && prodMatrix.length > 1) {
		var pmin = null;
		var pmax = null;
		for(y=0; y<prodMatrix[x].length; y++) {
			var prodIdx = prodMatrix[x][y];
			if(prodIdx != null) {
				var py = prodOpts[prodIdx].price;
				if(pmin == null || py < pmin) {
					pmin = py;
				}
				if(pmax == null || py > pmax) {
					pmax = py;
				}
			}
		}
		if(pmin == pmax) {
			sizePriceLabelText=formatDollars(pmin);
		} else {
			sizePriceLabelText=formatDollars(pmin) + " - " + formatDollars(pmax);
		}
	}
	sizePriceLabel.innerHTML=sizePriceLabelText;
}

function synchColorwayText(y) {
	var labels = cwOpts[y].names;
	for(var i=0; i<labels.length; i++) {
		document.getElementById('cw_text_' + i).innerHTML=labels[i];
	}
}

// A = active (click disabled), I= inactive (click enabled), H = hover (+inactive/enabled)
function setShoppingModeTabSeamStatus(element,newStatus) {
	var AI = "PP-shopping-mode-seam-AI"; // 0=newStatus
	var IA = "PP-shopping-mode-seam-IA"; // 1
	var AH = "PP-shopping-mode-seam-AH"; // 2
	var HA = "PP-shopping-mode-seam-HA"; // 3
	setStatusMutex(element,newStatus,[AI,IA,AH,HA]);
}

// Shopping tab button status:
// 	0: active   - active, selected tab
// 	1: inactive - inactive, unselected tab
// 	2: hover    - inactive, unselected, moused over
function setShoppingModeTabStatus(element,newStatus) {
	var ACTIVE   = "PP-shopping-mode-button-active";
	var INACTIVE = "PP-shopping-mode-button-inactive";
	var HOVER    = "PP-shopping-mode-button-hover";
	setStatusMutex(element,newStatus,[ACTIVE,INACTIVE,HOVER]);
}

// Swatch and size button status:
// 	0: disabled - out of stock, not selected
// 	1: enabled  - in stock, not selected
// 	2: selected - in stock, selected, any mouse state (:hover in CSS)
// 	3: disabled-selected - selected, 
// 		but we're mousing over a size/colorway that puts this selection 
// 		out of stock, so fade it out along with the button itself to
// 		indicate unavailability.
function setButtonStatus(element,newStatus) {
	var DISABLED = "PP-button-disabled";
	var ENABLED  = "PP-button-enabled";
	var SELECTED = "PP-button-selected";
	var DISABLED_SELECTED = "PP-button-faded-selection";
	setStatusMutex(element,newStatus,[DISABLED,ENABLED,SELECTED,DISABLED_SELECTED]);
}

function getColorwayStatus(y) { // x: sizeIdx
	var status = 1;
	if(y == getSelectedColorwayIdx()) {
		status = 2;
	} else {
		if(isShopBySizeMode()) {
			var x = getSelectedSizeIdx();
			if(prodMatrix[x][y] === null) {
				status = 0;
			}
		}
	}
	return status;
}

function getSizeStatus(x) {
	var sizeIdx = getSelectedSizeIdx;
	var status = 1; // enabled
	if(x == sizeIdx) {
		status = 2;
	} else {
		if(!isShopBySizeMode()) {
			var y = getSelectedColorwayIdx();
			if(prodMatrix[x][y] === null) {
				status = 0;
			}
		}
	}
	return status;
}

function synchSizeStatus(y,shopBySize) { // y: colorway index.
	var sizeIdx = getSelectedSizeIdx();
	var len = sizeOpts.length;
	for(var x=0; x<len; x++) {
		var status = 0; // disabled

		if(shopBySize || (prodMatrix[x][y] !== null)) {
			status = 1; // enabled
			if(sizeIdx == x) {
				status = 2; // selected
			}
		} else if(sizeIdx == x) {
			status = 3; // selected in a different size, so disabled but highlighted
		}
		decorateSizeStatus(x,status);
	}
}

function synchColorwayStatus(x,shopBySize) { // synch colorways for sizeIdx x
	var colorwayIdx = getSelectedColorwayIdx();
	var len = prodMatrix[x].length;
	for(var y=0; y<len; y++) {
		var status = 0; // disabled

		if(!shopBySize || prodMatrix[x][y] !== null) {
			status = 1; // enabled
			if(colorwayIdx == y) {
				status = 2; // selected
			}
		} else if(colorwayIdx == y) {
			status = 3; // selected in a different size, so disabled but highlighted
		}
		decorateColorwayStatus(y,status);
	}
}

function fadeSizeButton(x,fadeIn) { // false to fade out
	var img = document.getElementById('size_button_img_' + x);
	if(fadeIn) {
		img.src = sizeOpts[x].buttonSrc;
	} else {
		img.src = sizeOpts[x].disabledButtonSrc;
	}
}

function fadeColorwayButton(y,fadeIn) { // false to fade out
	var img = document.getElementById('cw_button_img_' + y);
	if(fadeIn) {
		img.src = cwOpts[y].buttonSrc;
	} else {
		img.src = cwOpts[y].disabledButtonSrc;
	}
}

function decorateSizeStatus(x,status) { // status 0: disabled, 1: enabled, 2: enabled-selected, 3: disabled-selected
	var button = document.getElementById('size_button_' + x);
	var fadeIn = (status != 0 && status != 3);
	fadeSizeButton(x,fadeIn);
	setButtonStatus(button,status);
}

function decorateColorwayStatus(y,status) { // status 0: disabled, 1: enabled, 2: enabled-selected, 3: disabled-selected
	var button = document.getElementById('cw_button_' + y);
	var fadeIn = (status != 0 && status != 3);
	fadeColorwayButton(y,fadeIn);
	setButtonStatus(button,status);
}

function synchBrowserHistory() {
	YAHOO.util.History.navigate("product", getSerializedPPState()); 
}

function getSerializedPPState() { // return a YUI history state string
	var delim = "-";
	var x = getSelectedSizeIdx();
	var y = getSelectedColorwayIdx();
	var x_id = sizeOpts[x].id;
	var y_id = cwOpts[y].id;
	return "" + x_id + delim + y_id;
}

// YUI.History bookmarking integration - parses the PPState string,
// updates the product module's UI.
function PPStateChangeHandler (state) { 
	if(state !== null && state !== undefined && state != "") {
		var changed = false;
		var tokens = state.split("-");
		if(tokens.length == 2) {

			var sizeId = parseInt(tokens[0]);
			var cwId = parseInt(tokens[1]);

			for(var x=0; x<sizeOpts.length; x++) {
				if(sizeId == sizeOpts[x].id) {
					if(x != getSelectedSizeIdx()) {
						setSelectedSizeIdx(x);
						changed = true;
					}
					break;
				}
			}

			for(var y=0; y<cwOpts.length; y++) {
				if(cwId == cwOpts[y].id) {
					if(y != getSelectedColorwayIdx()) {
						setSelectedColorwayIdx(y);
						changed = true;
					}
					break;
				}
			}
		}
		if(changed) {
			synchProductFormField()
			synchSkusetView();
		}
	}
}

/* --------------------- PRODUCT INFO TABS --------------------- */

function synchProductTabs() {
	redrawTabHeader();
	redrawTabBody();
}

function redrawTabBody() {
	for(var t=0; t<tabCount; t++) {
		redrawTabContent(t);
	}
}

function redrawTabContent(t) {
	conditionallySetElementDisplayStyle(tabIds[t],(t==selectedTabIndex),'block','none');
}

function redrawTabHeader() {
	for(var t=0; t<tabCount; t++) {
		redrawTabButton(t);
	}
}

function getTabStatus(t) {
	var status = 1;
	if(t == selectedTabIndex) {
		status = 0;
	} else if(t == hoverTabIndex) {
		status = 2;
	}
	return status;
}

// [lr]: status of the tab to the left and right of this seam.
function getTabSeamStatus(l,r) {
	var s = null;
	if(l==0) {
		if(r==1) s=0;
		else if(r==2) s=2;
	} else if(l==1) {
		if(r==0) s=1;
		else if(r==1) s=4;
		else if(r==2) s=5;
	} else if(l==2) {
		if(r==1) s=6;
		else if(r==0) s=3;
	}

	if(s==null) {
		var err = "Error in getTabStatus. l=" + l + " r=" + r;
		throw err;
	}
	return s;
}

function redrawTabButton(t) {
	var status = getTabStatus(t);

	if(t == 0) {
		var lmargin = document.getElementById('PP_tab_margin_left');
		setProductTabStatus(lmargin,status);
	}

	var tab = document.getElementById("PP_tab_button_" + t);
	setProductTabStatus(tab,status);

	if(t < tabCount - 1) {
		var seam = document.getElementById("PP_tab_seam_" + t);
		var seamStatus = getTabSeamStatus(status, getTabStatus(t+1));
		setProductTabSeamStatus(seam,seamStatus);
	} else {
		var rmargin = document.getElementById('PP_tab_margin_right');
		setProductTabStatus(rmargin,status);
	}
}

function productTabMousedOver(t) {
	if(!PP_initialized) return;
	if(t != selectedTabIndex) {
		hoverTabIndex = t;
		synchProductTabs();
	}
}

function productTabMousedOut(t) {
	if(!PP_initialized) return;
	if(t != selectedTabIndex) {
		hoverTabIndex = null;
		synchProductTabs();
	}
}

function productTabClicked(t) {
	if(!PP_initialized) return;
	if(t != selectedTabIndex) {
		hoverTabIndex = null;
		selectedTabIndex = t;
		synchProductTabs();
	}
}

// A = active (click disabled), I= inactive (click enabled), H = hover (+inactive/enabled)
function setProductTabSeamStatus(element,newStatus) {
	var AI = "PP-tab-seam-AI"; // 0=newStatus
	var IA = "PP-tab-seam-IA"; // 1
	var AH = "PP-tab-seam-AH"; // 2
	var HA = "PP-tab-seam-HA"; // 3
	var II = "PP-tab-seam-II"; // 4
	var IH = "PP-tab-seam-IH"; // 5
	var HI = "PP-tab-seam-HI"; // 6
	setStatusMutex(element,newStatus,[AI,IA,AH,HA,II,IH,HI]);
}

// Shopping mode button status:
// 	0: active   - selected, cannot click
// 	1: inactive - unselected, can click
// 	2: hover    - inactive, unselected, moused over
function setProductTabStatus(element,newStatus) {
	var ACTIVE  = "PP-tab-active";
	var INACTIVE= "PP-tab-inactive";
	var HOVER   = "PP-tab-hover";
	setStatusMutex(element,newStatus,[ACTIVE,INACTIVE,HOVER]);
}

function scrollViewportToTabView() {
	scrollViewportToElementId('PP_tab_container',180,66);
}

// Scroll so that elementId is in view.
// topMargin is selected from the target scroll offset. This
// lets us keep visual references in view, so the user doesn't
// think we've jumped to a whole new page.
// extraYOffset lets us make up for differences in position between
// the target element and its text label, for example.
function scrollViewportToElementId(elementId,topMargin,extraYOffset) {
	var elem = document.getElementById(elementId);
	if(elem !== null && elem !== undefined) {
		var view_h = YAHOO.util.Dom.getViewportHeight();
		if(view_h < topMargin) {
			// if the window gets extremely small, sacrifice
			topMargin = view_h;
		}
		var target_y = YAHOO.util.Dom.getY(elem) - topMargin + extraYOffset;
		var current_y = YAHOO.util.Dom.getDocumentScrollTop();
		var offset_y = target_y - current_y;
		window.scrollBy(0, offset_y);
	}
}